JavaScript로 개발을 하다 보면 중첩 배열의 데이터를 특정 기준에 따라 묶어야 하는 경우가 자주 발생합니다. 이번 글에서는 배열의 첫 번째 요소가 같은 값끼리 두 번째 요소들을 하나의 배열로 그룹화하는 방법을 알아보겠습니다.
문제 상황
다음과 같이 숫자 쌍으로 이루어진 중첩 배열이 있다고 가정해 봅시다.
const arr = [[1, 45], [1, 34], [1, 49], [2, 34], [4, 78], [2, 67], [4, 65]];
각 하위 배열은 반드시 정확히 두 개의 요소를 가지고 있습니다. 여기서 우리가 작성해야 할 함수는 다음과 같은 역할을 수행해야 합니다.
- 하위 배열들의 첫 번째 값(first value)을 기준으로 판단합니다.
- 첫 번째 값이 서로 같은 하위 배열들의 두 번째 값(second value)들을 모아 하나의 새로운 배열로 만듭니다.
따라서 위 배열을 처리한 결과는 아래와 같은 형태가 되어야 합니다.
const output = [ [45, 34, 49], [34, 67], [78, 65] ];
첫 번째 값이 1인 요소들은 [45, 34, 49]로, 2인 요소들은 [34, 67]로, 4인 요소들은 [78, 65]로 각각 그룹화된 것을 확인할 수 있습니다.
해결 방법: reduce()와 Map() 활용하기
이 문제는 Array.prototype.reduce() 메서드와 Map()> 객체를 함께 사용하면 깔끔하게 해결할 수 있습니다.
- reduce(): 배열을 순회하면서 누적 결과(accumulator)를 만들어 갑니다.
- Map()>: 첫 번째 값과 결과 배열 내 인덱스를 매핑하여, 이미 존재하는 그룹인지 빠르게 확인합니다.
코드 구현
전체 코드는 다음과 같습니다.
const arr = [[1, 45], [1, 34], [1, 49], [2, 34], [4, 78], [2, 67], [4, 65]];
const constructSimilarArray = (arr = []) => {
const creds = arr.reduce((acc, val) => {
const { map, res } = acc;
if(!map.has(val[0])){
map.set(val[0], res.push([val[1]]) - 1);
}else{
res[map.get(val[0])].push(val[1]);
};
return { map, res };
}, {
map: new Map(),
res: []
});
return creds.res;
};
console.log(constructSimilarArray(arr));동작 원리 살펴보기
- 초기값 설정: reduce()의 초기값으로
map(그룹 키와 인덱스를 저장하는 Map 객체)과res(최종 결과 배열)를 담은 객체를 전달합니다. - 새로운 그룹인 경우:
map.has(val[0])가 false라면, 해당 첫 번째 값이 처음 등장한 것이므로res에 새 배열을 추가하고, 그 인덱스를 Map에 저장합니다. 참고로 JavaScript의push()메서드는 배열의 새 길이를 반환하므로, 여기서 1을 빼면 방금 추가된 요소의 실제 인덱스가 됩니다. - 기존 그룹인 경우: Map에 이미 키가 존재한다면, 저장된 인덱스를 이용해
res의 해당 그룹 배열에 두 번째 값을 추가(push)합니다. - 결과 반환: 순회가 끝나면
creds.res, 즉 그룹화된 최종 배열을 반환합니다.
실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
[ [ 45, 34, 49 ], [ 34, 67 ], [ 78, 65 ] ]
마무리
이처럼 reduce()와 Map()>을 조합하면 별도의 외부 변수 없이 함수형 프로그래밍 스타일로 데이터를 그룹화할 수 있습니다. 이 패턴은 키 기반 데이터 집계, 카테고리별 분류 등 다양한 상황에서 응용할 수 있으니 꼭 기억해 두시기 바랍니다.