문제 상황
다음과 같이 객체로 이루어진 배열이 있다고 가정해 보겠습니다.
const arr = [
{
assigned_user:{
name:'Paul',
id: 34158
},
doc_status: "processed"
},
{
assigned_user:{
name:'Simon',
id: 48569
},
doc_status: "processed"
},
{
assigned_user:{
name:'Simon',
id: 48569
},
doc_status: "processed"
}
];위 배열에는 'Paul'이 한 번, 'Simon'이 두 번 등장합니다. 우리가 작성해야 할 함수는 이러한 객체 배열을 입력으로 받아, assigned_user 속성에 포함된 고유한 사용자별로 등장 횟수를 집계하는 것입니다.
즉, 중복되지 않는 각 사용자 이름을 키로 삼고, 해당 사용자가 배열에 몇 번 나타나는지를 값으로 갖는 결과를 만들어 반환해야 합니다.
reduce()를 활용한 해결 방법
가장 일반적인 접근 방식은 배열 메서드인 reduce()를 사용하는 것입니다. 빈 객체를 초기값으로 설정한 뒤, 배열을 순회하면서 각 사용자 이름을 키로 사용해 카운트를 누적하면 됩니다.
const countUnique = (arr = []) => {
let res = [];
res = arr.reduce(function (r, o) {
let user = o.assigned_user.name;
(r[user]) ? ++r[user] : r[user] = 1;
return r;
}, {}),
result = Object.keys(res).map(function (k) {
return {user: k, count: res[k]};
});
return res;
}
console.log(countUnique(arr));코드 동작 원리
reduce()는 배열의 각 요소를 순회하며 누적값(r)을 업데이트합니다. 현재 객체의 assigned_user.name 값을 추출한 후, 해당 키가 누적 객체에 이미 존재하면 카운트를 1 증가시키고(++r[user]), 존재하지 않으면 1로 초기화합니다(r[user] = 1). 모든 요소를 처리하면 사용자별 개수가 담긴 객체가 완성됩니다.
실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
{ Paul: 1, Simon: 2 }'Paul'은 한 번, 'Simon'은 두 번 등장했으므로 정확하게 집계된 것을 확인할 수 있습니다.
참고: 결과를 배열 형태로 얻기
위 코드의 result 변수처럼 Object.keys()와 map()을 조합하면, 단순 객체 대신 다음과 같은 객체 배열 형태로도 결과를 얻을 수 있습니다.
[
{ user: 'Paul', count: 1 },
{ user: 'Simon', count: 2 }
]반환값만 res 대신 result로 바꾸면 되므로, 용도에 맞게 선택하여 사용하면 됩니다.
Map을 활용한 현대적인 대안
ES6 이상 환경이라면 Map을 사용하는 방법도 좋습니다. 키 관리가 더 명확하고 성능 면에서도 유리합니다.
const countUnique = (arr = []) => {
const map = new Map();
arr.forEach(({ assigned_user }) => {
const user = assigned_user.name;
map.set(user, (map.get(user) || 0) + 1);
});
return Object.fromEntries(map);
};
console.log(countUnique(arr)); // { Paul: 1, Simon: 2 }두 방법 모두 시간 복잡도는 O(n)으로, 배열을 한 번만 순회하면 되기 때문에 대용량 데이터에서도 효율적으로 동작합니다.