Computer >> 컴퓨터 >  >> 프로그래밍 >> JavaScript

JavaScript 배열을 tableName 기준으로 그룹화하는 방법

데이터를 다루다 보면 여러 테이블의 정보가 하나의 배열에 섞여 있는 경우가 많습니다. 이럴 때 특정 키를 기준으로 데이터를 묶어주면 훨씬 다루기 쉬워집니다.

예를 들어 다음과 같은 JavaScript 배열이 있다고 가정해 보겠습니다.

const data = [
    {
        "dataId": "1",
        "tableName": "table1",
        "column": "firstHeader",
        "rows": ["a", "b", "c"]
    },
    {
        "dataId": "2",
        "tableName": "table1",
        "column": "secondHeader",
        "rows": ["d", "e", "f"]
    },
    {
        "dataId": "3",
        "tableName": "table2",
        "column": "aNewFirstHeader",
        "rows": [1, 2, 3]
    }
];

요구 사항

이 배열을 입력으로 받아, tableName 값이 같은 객체들을 하나로 묶은 새로운 배열을 반환하는 함수를 작성해야 합니다. 즉, 고유한 tableName마다 별도의 객체를 만들고, 그 안에 해당 테이블의 column 목록과 rows 데이터를 모아 담는 것입니다.

최종적으로 기대하는 출력 결과는 다음과 같습니다.

const output = [
    {
        "tableName": "table1",
        "column": ["firstHeader", "secondHeader"],
        "rows": [["a", "b", "c"], ["d", "e", "f"]]
    },
    {
        "tableName": "table2",
        "column": ["aNewFirstHeader"],
        "rows": [[1, 2, 3]]
    }
];

해결 방법: reduce와 Map 활용하기

배열의 reduce() 메서드와 Map 객체를 조합하면 한 번의 순회로 깔끔하게 그룹화할 수 있습니다.

const arr = [
    {
        "dataId": "1",
        "tableName": "table1",
        "column": "firstHeader",
        "rows": ["val", "b", "c"]
    },
    {
        "dataId": "2",
        "tableName": "table1",
        "column": "secondHeader",
        "rows": ["d", "e", "f"]
    },
    {
        "dataId": "3",
        "tableName": "table2",
        "column": "aNewFirstHeader",
        "rows": [1, 2, 3]
    }
];

const groupArray = (arr = []) => {
    const res = arr.reduce((obj => (acc, val) => {
        let item = obj.get(val.tableName);
        if (!item) {
            item = {
                tableName: val.tableName,
                column: [],
                rows: []
            };
            obj.set(val.tableName, item);
            acc.push(item);
        }
        item.column.push(val.column);
        item.rows.push(val.rows);
        return acc;
    })(new Map), []);
    return res;
};

console.log(JSON.stringify(groupArray(arr), undefined, 4));

코드 동작 원리

핵심 로직을 단계별로 살펴보면 다음과 같습니다.

1. Map으로 그룹 추적
클로저 내부에 Map 인스턴스를 생성해 tableName을 키로 사용합니다. 덕분에 각 요소를 순회하면서도 O(1)에 가까운 속도로 이미 만들어진 그룹이 있는지 확인할 수 있습니다.

2. 첫 등장 시 새 그룹 생성
현재 순회 중인 요소의 tableName이 Map에 없다면, tableName, 빈 column 배열, 빈 rows 배열을 가진 새 객체를 만들어 Map과 최종 결과 배열(acc)에 동시에 등록합니다.

3. 데이터 누적
그룹 객체가 준비되면 현재 요소의 column 값과 rows 배열을 각각 push하여 데이터를 누적합니다. 이 과정을 마지막 요소까지 반복하면 그룹화가 완료됩니다.

실행 결과

콘솔에 출력되는 결과는 다음과 같습니다.

[
    {
        "tableName": "table1",
        "column": [
            "firstHeader",
            "secondHeader"
        ],
        "rows": [
            ["val", "b", "c"],
            ["d", "e", "f"]
        ]
    },
    {
        "tableName": "table2",
        "column": [
            "aNewFirstHeader"
        ],
        "rows": [
            [1, 2, 3]
        ]
    }
]

참고: ES2024의 groupBy 활용하기

최신 JavaScript 환경(ES2024 이상)이라면 Object.groupBy() 또는 Map.groupBy()를 사용해 더 간결하게 구현할 수도 있습니다.

const grouped = Object.groupBy(arr, ({ tableName }) => tableName);

const result = Object.entries(grouped).map(([tableName, items]) => ({
    tableName,
    column: items.map(item => item.column),
    rows: items.map(item => item.rows)
}));

다만 아직 구형 브라우저나 런타임에서는 지원되지 않을 수 있으므로, 폭넓은 호환성이 필요하다면 앞서 소개한 reduce + Map 방식이 안전한 선택입니다.