https://tech.kakao.com/posts/344
이 문제는 자카드 유사도 J(a,b)
를 설명해주고 자카드유사도 값을 직접 계산하는 프로그램을 작성하는 문제이다.
자카드 유사도를 구하는 공식은 간단하다. 교집합을 합집합으로 나눈 수이다. 이 값은 늘 0~1사이의 실수가 되므로 65536을 곱한 후 소수점 아래를 버리고 정수부분만 취하도록 한다.
문제 설명은 원소의 중복을 허용하는 다중집합으로 되어 있다. 자주 접하는 자료구조가 아니여서 이부분이 어려웠다. 카카오 테크블로그의 문제해설에서는 다중집합 자료구조를 사용하지 않고 각 원소를 정렬된 배열에 넣어 병합정렬 코드를 응용하여 교집합과 합집합 함수를 구현하는 방법이 있다고 한다.
결론적으로는 주어진 문자열로 다중집합을 만들고, 각 집합의 교집합, 합집합을 구현해내는것이 관건인 문제였다.
몇가지 예외 케이스로는
집합의 원소가 될 문자쌍은 다음과 같이 만든다.
이를 바탕으로 교집합, 합집합을 만든다.
자카드 유사도를 구한다.
J("FRANCE", "FRENCH")
= 2/8 = 0.25그 외의 주의해야 할 규칙으로는
위 두가지 이유로 key: value
형태의 Map자료형을 활용하여 문자쌍:갯수
형태로 저장한다.
예를들어 {1,1,1,2,2,3}
의 경우 { 1:3, 2:2, 3:1 }
이 된다.
반복되는 작업이 많으므로 코드를 줄이기 위해 세부기능을 모듈화 하였다.
function makeCharPair(str) {
const arr = [];
for (let i = 0; i < str.length - 1; i++) {
arr.push(str1.slice(i, i + 2));
}
return arr;
}
function removeInvalidChar(arr) {
const regex = /[^a-zA-Z]/;
return arr.filter(elem => !regex.test(elem));
}
function countOccurency(arr) {
const map = new Map();
arr.forEach(el => {
map.set(el, (map.get(el) || 0) + 1); //NOTE
});
return map;
}
function getIntersection(map1, map2) {
const intersectionMap = new Map();
// NOTE : MAP순회
map1.forEach((value1, key) => {
// 교집합의 값이 있다면
if (map2.has(key)) {
// 더 적게 등장한 갯수를
intersectionMap.set(key, Math.min(value1, map2.get(key)));
}
});
return intersectionMap;
}
function getUnion(map1, map2) {
// return new Map([...map1, ...map2]); 이렇게 단순 합치면 테케 통과x
let union = new Map();
for (let [key, value] of map1.entries()) {
union.set(key, value);
}
for (let [key, value] of map2.entries()) {
if (map1.has(key)) {
// 겹치는 값이 있다면 큰 쪽을 택한다.
union.set(key, Math.max(value, map1.get(key)));
} else {
union.set(key, value);
}
}
return union;
}
각 모듈을 조합한 전체 solution함수는
function solution(strA, strB) {
const str1 = strA.toLowerCase();
const str2 = strB.toLowerCase();
// 주어진 문자열로 가능한 모든 문자쌍을 만든다.
let arr1 = makeCharPair(str1);
let arr2 = makeCharPair(str2);
//필요없는 문자쌍은 버린다.
arr1 = removeInvalidChar(arr1);
arr2 = removeInvalidChar(arr2);
// 각 배열별 요소의 등장 횟수를 저장한다.
let map1 = countOccurency(arr1);
let map2 = countOccurency(arr2);
// 둘다 공집합인경우
if (map1.size === 0 && map2.size === 0) {
return 65536;
}
// 교집합을 구한다.
const intersection = getIntersection(map1, map2);
// 합집합을 구한다.
const union = getUnion(map1, map2);
// 교집합이 0이면 0
if (intersection.size === 0 && union.size > 0) {
return 0;
}
const intersectionSize = [...intersection.values()].reduce((a, c) => a + c);
const unionSize = [...union.values()].reduce((a, c) => a + c);
// 실수 -> 정수부만 취하기
return ~~((intersectionSize / unionSize) * 65536);
}
regex.test(elem)
메서드||
의 활용(map.get(el) || 0) + 1
map1.forEach((value1, key) => {})
:Value가 먼저 온다!map.entries()
: 키 & 값
for (let [key, value] of map1.entries()) {
union.set(key, value);
}
map.keys()
: 키 Only
for (const key of myMap.keys()) {
console.log(Key: ${key});
}
map.values()
: 값 Only
for (const value of myMap.values()) {
console.log(Value: ${value});
}
Array.from(map)
혹은 전개연산자 [ … map ]