문제
(문제가 길어서 윗부분을 잘랐습니다.)
유사한 기사를 묶는 기준을 정하기 위해서 논문과 자료를 조사하던 튜브는 자카드 유사도라는 방법을 찾아냈다.
자카드 유사도는 집합 간의 유사도를 검사하는 여러 방법 중의 하나로 알려져 있다. 두 집합 A, B 사이의 자카드 유사도 J(A, B)는 두 집합의 교집합 크기를 두 집합의 합집합 크기로 나눈 값으로 정의된다.
예를 들어 집합 A = {1, 2, 3}, 집합 B = {2, 3, 4}라고 할 때, 교집합 A ∩ B = {2, 3}, 합집합 A ∪ B = {1, 2, 3, 4}이 되므로, 집합 A, B 사이의 자카드 유사도 J(A, B) = 2/4 = 0.5가 된다. 집합 A와 집합 B가 모두 공집합일 경우에는 나눗셈이 정의되지 않으니 따로 J(A, B) = 1로 정의한다.
자카드 유사도는 원소의 중복을 허용하는 다중집합에 대해서 확장할 수 있다. 다중집합 A는 원소 1을 3개 가지고 있고, 다중집합 B는 원소 1을 5개 가지고 있다고 하자. 이 다중집합의 교집합 A ∩ B는 원소 1을 min(3, 5)인 3개, 합집합 A ∪ B는 원소 1을 max(3, 5)인 5개 가지게 된다. 다중집합 A = {1, 1, 2, 2, 3}, 다중집합 B = {1, 2, 2, 4, 5}라고 하면, 교집합 A ∩ B = {1, 2, 2}, 합집합 A ∪ B = {1, 1, 2, 2, 3, 4, 5}가 되므로, 자카드 유사도 J(A, B) = 3/7, 약 0.42가 된다.
이를 이용하여 문자열 사이의 유사도를 계산하는데 이용할 수 있다. 문자열 FRANCE와 FRENCH가 주어졌을 때, 이를 두 글자씩 끊어서 다중집합을 만들 수 있다. 각각 {FR, RA, AN, NC, CE}, {FR, RE, EN, NC, CH}가 되며, 교집합은 {FR, NC}, 합집합은 {FR, RA, AN, NC, CE, RE, EN, CH}가 되므로, 두 문자열 사이의 자카드 유사도 J("FRANCE", "FRENCH") = 2/8 = 0.25가 된다.
- 입력 형식
입력으로는 str1과 str2의 두 문자열이 들어온다. 각 문자열의 길이는 2 이상, 1,000 이하이다.
입력으로 들어온 문자열은 두 글자씩 끊어서 다중집합의 원소로 만든다. 이때 영문자로 된 글자 쌍만 유효하고, 기타 공백이나 숫자, 특수 문자가 들어있는 경우는 그 글자 쌍을 버린다. 예를 들어 ab+가 입력으로 들어오면, ab만 다중집합의 원소로 삼고, b+는 버린다.
다중집합 원소 사이를 비교할 때, 대문자와 소문자의 차이는 무시한다. AB와 Ab, ab는 같은 원소로 취급한다. - 출력 형식
입력으로 들어온 두 문자열의 자카드 유사도를 출력한다. 유사도 값은 0에서 1 사이의 실수이므로, 이를 다루기 쉽도록 65536을 곱한 후에 소수점 아래를 버리고 정수부만 출력한다.풀이과정
- 먼저 str1과 str2를 모두 소문자(또는 모두 대문자)로 변경한다.
- str1과 str2를 두 글자씩 끊어서 다중 집합의 원소로 만든다.
- 이때, 공백이나 숫자, 특수문자가 들어오는 경우에는 해당 쌍을 버린다. (ex.
ab+b -> {'ab', 'b+', '+b'} -> {'ab'}
)
- 이때, 공백이나 숫자, 특수문자가 들어오는 경우에는 해당 쌍을 버린다. (ex.
- 교집합을 구한다. 이 때, 중복원소를 꼭 체크해주어야한다.
- 중복원소의 경우에는 더 작은 개수를 채택
- 합집합을 구한다. 이 때, 중복원소를 체크해주어야한다.
- 중복원소의 경우에는 더 많은 개수를 채택
필수 예외 처리 사항
- str1과 str2를 두 글자씩 끊어서 만든 두 다중 집합이 모두 공집합이면 1(즉 65536)을 리턴
- 교집합 개수가 0이면 0을 리턴
소스코드
소요 시간 32:56
(예외 처리에 오래 걸렸다.. 문제 제대로 읽자)
def cut_two_letter(s):
res = dict()
for i in range(1,len(s)):
if s[i-1].isalpha() and s[i].isalpha():
if s[i-1]+s[i] in res.keys():
res[s[i-1]+s[i]] += 1
else:
res[s[i-1]+s[i]] = 1
return res
def get_intersection(s1, s2):
total = 0
for key, value in s1.items():
if key in s2.keys(): #s2에 존재한다면
total += min(s1[key], s2[key])
return total
def get_union(s1, s2):
total = sum(list(s1.values())) + sum(list(s2.values()))
for key, value in s1.items():
if key in s2.keys():
total -= min(s1[key], s2[key])
return total
def solution(str1, str2):
str1, str2 = str1.lower(), str2.lower() #모두 소문자로 변경한다.
set1, set2 = {}, {}
#두 글자씩 끊기
set1 = cut_two_letter(str1)
set2 = cut_two_letter(str2)
if not set1 and not set2:
return 65536
i = get_intersection(set1, set2)
u = get_union(set1, set2)
if i == 0:
return 0
answer = int((i/u) * 65536)
return answer