[Java] 두 문자열간의 유사도 구하기

두개의 문자열이 있을때, 얼마나 유사한지를 백분율의 개념인 0~1사이의 값으로 확인할 수 있을까? 즉 똑같은 문자열이라면 1을 전혀 다른 문자열이라면 0이라는 값으로 말이다. 구글링해보니 edit distance 계산을 통해 얻을 수 있단다. 가장 일반적은 구현체는 Levenshtein의 Distance Algorithm이라고 하고, 그 구현 함수는 다음과 같다. (출처: http://rosettacode.org/wiki/Levenshtein_distance#Java)

private double similarity(String s1, String s2) {
    String longer = s1, shorter = s2;
	
    if (s1.length() < s2.length()) {
        longer = s2; 
        shorter = s1;
    }
	
    int longerLength = longer.length();
    if (longerLength == 0) return 1.0;

    return (longerLength - editDistance(longer, shorter)) / (double) longerLength;
}

private int editDistance(String s1, String s2) {
	s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();
    int[] costs = new int[s2.length() + 1];
    
    for (int i = 0; i <= s1.length(); i++) {
        int lastValue = i;
        for (int j = 0; j <= s2.length(); j++) {
            if (i == 0) {
            	costs[j] = j;
            } else {
                if (j > 0) {
                    int newValue = costs[j - 1];
                    
                    if (s1.charAt(i - 1) != s2.charAt(j - 1)) {
                    	newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
                    }
                    
                    costs[j - 1] = lastValue;
                    lastValue = newValue;
                }
            }
        }
        
        if (i > 0) costs[s2.length()] = lastValue;
    }
    
    return costs[s2.length()];
}

사용은 similarity 함수에 비교할 문자열 2개를 지정하면 비슷한 정도가 0~1 사이의 값으로 반환된다.

Python에서 외부 데이터 파일 읽기

파이썬에서 다양한 데이터 파일을 읽어오는 코드를 정리한 글로 추후 빠르게 참조하기 위한 목적으로 작성 되었습니다.

CSV 파일 읽기

import csv

rows = []
with open('../data/fileName.csv') as csvfile:
    csvreader = csv.reader(csvfile)
    next(csvreader, None)
    for row in csvreader:
        rows.append(row)

3번의 next 함수는 csv 파일의 첫줄에 있는 필드명을 건너뛰기 위함입니다. rows에 데이터가 저장됩니다.

JSON 파일 읽기

import json

with open('../data/fileName.json') as jsonfile:
    data = json.load(jsonfile)
    value_plainType = data["key1"]
    value_arrayType = data["key2"]
    value_dictionaryType = data["key3"]

    print(value_plainType)
    print(value_arrayType)
    print(value_dictionaryType["name"])

위의 fileName.json 파일의 내용이 다음과 같을때..

{
    "key1" : "string/numeric/bool",
    "key2" : [1, 2, 3, 4, 5],
    "key3" : { "name":"DoWise", "age": 12 }
}

출력 결과는 다음과 같습니다.

string/numeric/bool
[1, 2, 3, 4, 5]
DoWise

Python에서 객체에 대한 key, value 조회

key, value 자료 구조는 매우 효율적인으로 데이터를 저장하고 빠르게 검색할 수 있는 구조입니다. Python에서도 제공하는 key, value 자료구조는 Javascript와 매우 유사한데요. 예를들어 다음처럼 key와 value를 자유롭게 구성할 수 있습니다.

book = {
    'Boy': (100, 200),
    'Guy': (200, 400),
    'Girl': (300, 150),
    'Woman': (400, 800)
}

총 4개의 요소로 구성되어 있는데.. 각 요소를 순회하면서 조회하기 위한 코드는 다음과 같습니다.

for word, pages in book.items():
    print(word, pages)

위의 실행 결과는 다음과 같습니다.

Boy (100, 200)
Guy (200, 400)
Girl (300, 150)
Woman (400, 800)

앞서 book 변수에 대한 타입명은 dictionary입니다. 이 타입에서 key 요소는 중복될 수 없습니다.

특정 key 값을 갖는 요소를 제거하기 위해서는, 예를들어 key가 ‘Boy’ 인 요소를 제거하기 위한 코드는 다음과 같습니다.

del book['Boy']

또한 특정 키를 가진 요소가 존재하는지에 대한 검사는 다음과 같습니다.

print('Boy' in book)
print('Girl' in book)

앞서 key가 ‘Boy’인 요소는 제거되었으므로, 위의 코드에 대한 결과는 다음과 같습니다.

False
True

처리해야 할 공백 문자가 있다면, 꼭 고려해야 할 ‘ZERO WIDTH SPACE’

Code 값 32는 가장 흔히 볼 수 볼 수 있는 공백문자. 160도 공백문자인데, nbsp(Non-breaking Space) 문자라고 한다. 여기에 하나더 Code 값 8203이 있는데 이 값 역시 공백문자이다. 그런데 공백을 차지 하지 않는 공백문자, ‘ZERO WIDTH SPACE’라고 한단다. 보이지 않는 공백문자, 다른 말로 공백이 아닌 공백 문자이다. 참고로 유니코드 문자셋이다.

아래의 코드는 Javascript에서 Space 문자를 제거하는 코드이다.

let address = '공백 문자를 포함하는 문장';
let arrAddress = [];
for (let i = 0; i < address.length; i++) {
    let charCode = address.charCodeAt(i);
    if (charCode === 8203 /* Unicode Character 'ZERO WIDTH SPACE' */ || 
        charCode === 160 /* nbsp(non-breaking space) */ || 
        charCode === 32 /* Space */) {
        // skips all space chars
    } else {
        arrAddress.push(address[i]);
    }
}
address = arrAddress.join("");

이 글에서 언급하는 Space 문자로 3개 언급했는데.. 또 있다면 코드에 반영해야 할 것이다. 참고로 Javascript에서 문자열의 실행중 변경은 배열을 사용해야 한다. 즉, Java의 StringBuilder의 용도와 동일하다.

Python의 Thread API

Python의 언어적 한계로 Thread 기능의 지원이 적합하진 않다고 하지만, 문제점을 최대한 개선하여 GIL이라는 장치를 통해 지원한다고 합니다. GIL은 Global Interpreter Lock의 약자입니다. 코드는 다음과 같습니다.

import threading

class T(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        pass

    def run(self):
        for i in range(100):
             print(self.name, i)

13번 줄의 코드인 self.name은 Thread-n과 같은 스레드의 내부 이름이며, 여기에서 n은 내부 번호입니다. 앞서 정의한 스레드 T의 사용은 다음과 같습니다.

ts = [T() for i in range(4)]

for t in ts:
    t.start()

총 4개의 스레드를 생성하고 start 매서드를 통해 스레드를 시작합니다.