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

JavaScript에서 인덱스 경로를 이용해 중첩 배열의 항목 제거하기

JavaScript로 데이터를 다루다 보면 객체와 배열이 여러 겹으로 중첩된 구조에서 특정 위치의 항목을 제거해야 하는 경우가 종종 있습니다. 이번 글에서는 인덱스 경로 배열을 기반으로 중첩 배열 안의 항목을 재귀적으로 찾아 삭제하는 방법을 알아보겠습니다.

문제 상황

다음과 같이 객체들이 중첩된 배열이 있다고 가정해 보겠습니다.

const arr = [
    { value: 'some value' },
    {
        array: [
            { value: 'some value' },
            {
                 array: [
                     { value: 'some value' },
                     { value: 'some value' },
                 ],
            },
            { value: 'some value' },
            {
                 array: [
                     { value: 'some value' },
                     {
                          array: [
                              { value: 'delete me' },
                              { value: 'some value' },
                          ]
                     },
                 ],
            },
        ],
    }
];

여기서 우리가 작성해야 할 함수는 다음 두 가지 인자를 받습니다.

  • 첫 번째 인자: 위와 같은 중첩 배열
  • 두 번째 인자: 삭제할 항목의 경로를 나타내는 인덱스 배열

함수는 두 번째 인자로 전달된 인덱스 경로에 해당하는 항목을 찾아 제거한 새로운 배열을 반환해야 합니다.

해결 방법: 재귀 함수 활용

중첩 구조는 깊이가 정해져 있지 않기 때문에 반복문만으로 처리하기 어렵습니다. 따라서 재귀(recursion)를 사용하면 깊이에 상관없이 유연하게 대응할 수 있습니다.

핵심 로직은 다음과 같습니다.

  1. 현재 레벨의 배열을 순회하면서 각 요소의 인덱스가 목표 인덱스와 일치하는지 확인합니다.
  2. 일치하지 않으면 그대로 결과 배열에 추가합니다.
  3. 일치하고, 아직 마지막 레벨이 아니며 해당 요소가 하위 array 속성을 가지고 있다면, 한 단계 더 들어가 재귀 호출을 수행합니다.
  4. 마지막 레벨에서 일치하는 항목은 결과 배열에 포함시키지 않음으로써 자연스럽게 제거됩니다.

구현 코드

const arr = [
    { value: 'some value' },
    {
        array: [
            { value: 'some value' },
            {
                 array: [
                     { value: 'some value' },
                     { value: 'some value' },
                 ],
            },
            { value: 'some value' },
            {
                 array: [
                     { value: 'some value' },
                     {
                          array: [
                              { value: 'delete me' },
                              { value: 'some value' },
                          ]
                     },
                 ],
            },
        ],
    }
];
const keys = [1, 3, 1, 0];

const getKeys = (arr, keys) => {
    const recursiveFind = (arr, level) => {
        const res = [];
        arr.forEach((el, ind) => {
            // 현재 레벨의 목표 인덱스와 일치하지 않으면 그대로 유지
            if (keys[level] !== ind) {
                return res.push(el);
            };
            // 일치하지만 아직 탐색할 하위 레벨이 남아 있다면 재귀 호출
            if (level + 1 !== keys.length && el.array) {
                res.push({ array: recursiveFind(el.array, level + 1) });
            };
        });
        return res;
    };
    return recursiveFind(arr, 0);
};

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

실행 결과

위 코드를 실행하면 콘솔에 다음과 같은 출력이 나타납니다. 인덱스 경로 [1, 3, 1, 0]에 해당했던 'delete me' 항목이 사라진 것을 확인할 수 있습니다.

[
    {
        "value": "some value"
    },
    {
        "array": [
            {
                 "value": "some value"
            },
            {
                 "array": [
                     {
                          "value": "some value"
                     },
                     {
                          "value": "some value"
                     }
                 ]
            },
            {
                 "value": "some value"
            },
            {
                 "array": [
                     {
                          "value": "some value"
                     },
                     {
                          "array": [
                              {
                                  "value": "some value"
                              }
                          ]
                     }
                 ]
            }
        ]
    }
]

마무리

이 방식은 원본 배열을 변경하지 않고 새로운 배열을 생성하기 때문에 불변성(immutability)을 유지해야 하는 React나 Redux 같은 환경에서도 안전하게 사용할 수 있습니다. 또한 인덱스 경로의 길이만큼만 재귀가 진행되므로 성능 면에서도 효율적입니다. 트리 형태의 데이터 구조(예: 폴더 구조, 카테고리 트리, 메뉴 계층)에서 특정 노드를 경로 기반으로 삭제할 때 유용하게 활용할 수 있습니다.