문제 소개
JavaScript로 데이터를 다루다 보면 객체 배열을 특정 기준으로 그룹화해야 하는 경우가 자주 발생합니다. 이번 글에서는 region(지역) 속성을 기준으로 객체 배열을 그룹화하고, 동시에 각 지역별 고유 사용자 수를 집계하는 방법을 알아보겠습니다.
먼저 다음과 같은 객체 배열이 있다고 가정해 보겠습니다.
const arr = [
{ region: "Africa", fruit: "Orange", user: "Gary" },
{ region: "Africa", fruit: "Apple", user: "Steve" },
{ region: "Europe", fruit: "Orange", user: "John" },
{ region: "Europe", fruit: "Apple", user: "bob" },
{ region: "Asia", fruit: "Orange", user: "Ian" },
{ region: "Asia", fruit: "Apple", user: "Angelo" },
{ region: "Africa", fruit: "Orange", user: "Gary" }
];요구 사항
위 배열을 입력으로 받아 다음 작업을 수행하는 JavaScript 함수를 작성해야 합니다.
- 객체의
region속성을 기준으로 데이터를 그룹화한 새로운 배열을 생성합니다. - 각 지역별로 중복되지 않는 고유 사용자의 수를 함께 계산합니다.
예를 들어 위 배열에서 'Africa'에는 Gary와 Steve 두 명의 고유 사용자가 있으므로 count는 2가 됩니다. 마지막 요소의 Gary는 중복이므로 다시 세지 않습니다.
따라서 최종 출력 결과는 다음과 같은 형태가 되어야 합니다.
const output = [
{
"region": "Africa",
"count": 2
},
{
"region": "Europe",
"count": 2
},
{
"region": "Asia",
"count": 2
}
];해결 코드
이 문제의 핵심은 이미 확인한 지역과 사용자 조합을 추적하는 것입니다. 아래 코드에서는 프로토타입 오염을 방지하기 위해 Object.create(null)로 생성한 순수 객체를 조회 테이블(lookup table)로 활용합니다.
const arr = [
{ region: "Africa", fruit: "Orange", user: "Gary" },
{ region: "Africa", fruit: "Apple", user: "Steve" },
{ region: "Europe", fruit: "Orange", user: "John" },
{ region: "Europe", fruit: "Apple", user: "bob" },
{ region: "Asia", fruit: "Orange", user: "Ian" },
{ region: "Asia", fruit: "Apple", user: "Angelo" },
{ region: "Africa", fruit: "Orange", user: "Gary" }
];
const groupByArea = (arr = []) => {
const res = [];
// 지역 및 지역+사용자 조합을 추적하기 위한 조회 테이블
const seen = Object.create(null);
arr.forEach(el => {
// 해당 지역을 처음 만나면 결과 배열에 추가
if (!seen[el.region]) {
seen[el.region] = { region: el.region, count: 0 };
res.push(seen[el.region]);
}
// 지역과 사용자를 조합한 고유 키 생성
const key = [el.region, el.user].join('|');
// 처음 보는 조합이라면 고유 사용자로 간주하고 카운트 증가
if (!seen[key]) {
seen[key] = true;
seen[el.region].count++;
}
});
return res;
};
console.log(groupByArea(arr));코드 동작 원리
- 조회 테이블 초기화:
Object.create(null)로 생성한seen객체는 프로토타입 체인이 없어 안전하게 키를 저장할 수 있습니다. - 지역 그룹 생성: 배열을 순회하면서 처음 등장하는 지역이면
{ region, count: 0 }객체를 만들어 결과 배열에 추가합니다. - 고유 키 생성: 지역명과 사용자명을
|구분자로 연결하여 고유 키를 만듭니다. 이렇게 하면 같은 이름의 사용자가 다른 지역에 있어도 정확히 구분됩니다. - 카운트 증가: 해당 키가 처음 등장하는 경우에만 카운트를 증가시키므로, 중복된 사용자(Gary의 두 번째 등장)는 자동으로 제외됩니다.
실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
[
{ region: 'Africa', count: 2 },
{ region: 'Europe', count: 2 },
{ region: 'Asia', count: 2 }
]마무리
이처럼 조회 테이블 하나만 잘 활용하면 복잡해 보이는 그룹화·집계 문제도 O(n) 시간 복잡도로 깔끔하게 해결할 수 있습니다. 실무에서는 ES2024에 도입된 Object.groupBy()나 Map.groupBy() 메서드를 활용하면 더욱 간결하게 그룹화 로직을 작성할 수 있으니 함께 참고하시기 바랍니다.