문제 상황
알파벳 문자들이 담긴 배열이 있고, 그중 일부 알파벳은 여러 번 반복되어 있다고 가정해 보겠습니다.
const arr = [ 'a','a','a','a','d','e','e','f','h','h','h','i','l','m','n', 'r','s','s','t','u','v','y','y' ];
이런 배열을 입력받아 동일한 알파벳끼리 각각의 하위 배열(subarray)로 묶어주는 JavaScript 함수를 작성해야 합니다.
즉, 위 배열에 대해 함수가 반환해야 하는 결과는 다음과 같습니다.
const output = [ ['a','a','a','a'], ['d'], ['e','e'], ['f'], ['h','h','h'], ['i'], ['l'], ['m'], ['n'], ['r'], ['s','s'], ['t'], ['u'], ['v'], ['y','y'] ];
해결 방법: reduce와 해시 객체 활용하기
이 문제는 reduce() 메서드와 해시(hash) 객체를 조합하면 깔끔하게 해결할 수 있습니다. 핵심 아이디어는 다음과 같습니다.
- 해시 객체: 각 알파벳이 결과 배열에서 몇 번째 인덱스(그룹)에 속하는지 기록합니다.
- reduce 누적기(res): 실제 그룹화된 결과 배열을 쌓아갑니다.
요소를 순회하면서 처음 등장한 알파벳이라면 새로운 하위 배열을 만들고, 이미 등장한 적이 있다면 해시에 저장된 인덱스를 이용해 해당 그룹에 요소를 추가(push)하면 됩니다.
코드 예제
const arr = [
'a','a','a','a','d','e','e','f','h','h','h','i','l','m','n',
'r','s','s','t','u','v','y','y'
];
const bringAlong = (arr = []) => {
const hash = {};
return arr.reduce(function(res, e) {
if (hash[e] === undefined)
hash[e] = res.push([e]) - 1; // 새 그룹 생성 후 인덱스 저장
else
res[hash[e]].push(e); // 기존 그룹에 추가
return res;
}, []);
};
console.log(bringAlong(arr));실행 결과
위 코드를 실행하면 콘솔에 다음과 같이 출력됩니다.
[ [ 'a', 'a', 'a', 'a' ], [ 'd' ], [ 'e', 'e' ], [ 'f' ], [ 'h', 'h', 'h' ], [ 'i' ], [ 'l' ], [ 'm' ], [ 'n' ], [ 'r' ], [ 's', 's' ], [ 't' ], [ 'u' ], [ 'v' ], [ 'y', 'y' ] ]
동작 원리 정리
- 배열의 각 요소를
reduce()로 순회합니다. - 현재 요소가 해시 객체에 없으면, 빈 하위 배열 하나를 결과에 추가하고 그 인덱스를 해시에 기록합니다.
- 이미 해시에 존재하는 요소라면, 기록된 인덱스 위치의 하위 배열에 요소를 추가합니다.
- 모든 순회가 끝나면 동일한 값끼리 묶인 2차원 배열이 완성됩니다.
이 방식은 배열을 한 번만 순회하므로 시간 복잡도가 O(n)으로 효율적이며, 숫자·문자열 등 다양한 타입의 배열에도 그대로 응용할 수 있다는 장점이 있습니다.