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

JavaScript에서 다른 배열의 순서를 기준으로 배열 정렬하기

JavaScript로 개발하다 보면 하나의 배열을 또 다른 배열에 담긴 순서를 기준으로 정렬해야 하는 경우가 종종 있습니다. 예를 들어 다음과 같은 두 개의 배열이 있다고 가정해 보겠습니다.

const arr1 = [1, 3, 2, 4, 5, 6];
const arr2 = [1, 2, 5];

문제 정의

우리가 만들 함수는 이렇게 주어진 두 개의 배열을 인자로 받아, arr1의 모든 요소를 포함하되 arr2의 순서에 맞게 정렬된 새로운 배열을 반환해야 합니다.

정렬 규칙은 다음과 같습니다.

  • 두 배열에 모두 존재하는 요소는 arr2에서 등장하는 순서대로 배열의 앞쪽에 배치됩니다.
  • arr1에만 존재하는 요소들은 기존의 상대적인 순서를 그대로 유지한 채 뒤따르게 됩니다.

즉, 위 예제의 경우 결과는 [1, 2, 5, 3, 4, 6]이 되어야 합니다. 1, 2, 5는 arr2의 순서를 따르고, 나머지 3, 4, 6은 원래 순서를 유지합니다.

구현 코드

Array.prototype.sort() 메서드에 커스텀 비교 함수를 전달하여 이 문제를 해결할 수 있습니다.

const arr1 = [1, 3, 2, 4, 5, 6];
const arr2 = [1, 2, 5];

const sortByReference = (arr1, arr2) => {
  // 두 배열 모두에 존재하는 요소인지 확인하는 헬퍼 함수
  const inBoth = el => arr1.includes(el) && arr2.includes(el);

  const sorter = (a, b) => {
    // 둘 다 공통 요소라면 arr2에서의 인덱스 차이로 순서 결정
    if (inBoth(a) && inBoth(b)) {
      return arr2.indexOf(a) - arr2.indexOf(b);
    }
    // a만 공통 요소라면 a를 앞으로
    if (inBoth(a)) return -1;
    // b만 공통 요소라면 b를 앞으로
    if (inBoth(b)) return 1;
    // 둘 다 공통 요소가 아니면 기존 순서 유지
    return 0;
  };

  return [...arr1].sort(sorter);
};

console.log(sortByReference(arr1, arr2));

실행 결과

콘솔에는 다음과 같이 출력됩니다.

[ 1, 2, 5, 3, 4, 6 ]

코드 동작 원리

  1. inBoth 함수: includes()를 사용해 특정 요소가 arr1과 arr2 양쪽에 모두 존재하는지 판별합니다.
  2. 비교 함수(sorter): sort()는 인접한 두 요소 a, b를 비교할 때 이 함수의 반환값을 기준으로 순서를 결정합니다.
    • 음수 → a가 b보다 앞에 위치
    • 양수 → b가 a보다 앞에 위치
    • 0 → 순서 변경 없음
  3. 공통 요소 정렬: 두 요소가 모두 arr2에도 있다면 arr2.indexOf(a) - arr2.indexOf(b)로 arr2 내부의 인덱스 차이를 반환하여 참조 배열의 순서를 그대로 반영합니다.
  4. 원본 보호: [...arr1]처럼 스프레드 연산자로 복사본을 정렬하면 원본 배열이 변경되는 부작용을 피할 수 있습니다.

더 간결한 대안: filter() 활용

sort() 없이 filter()만으로도 같은 결과를 훨씬 직관적으로 얻을 수 있습니다.

const sortByReference = (arr1, arr2) => [
  // arr2 순서대로 공통 요소 추출
  ...arr2.filter(el => arr1.includes(el)),
  // arr1에만 있는 요소는 원래 순서대로 뒤에 붙임
  ...arr1.filter(el => !arr2.includes(el)),
];

console.log(sortByReference([1, 3, 2, 4, 5, 6], [1, 2, 5]));
// [ 1, 2, 5, 3, 4, 6 ]

이 방식은 로직이 명확해서 가독성이 뛰어나며, 별도의 비교 함수 작성 없이 의도를 바로 파악할 수 있다는 장점이 있습니다.

성능 팁

includes()indexOf()는 배열을 처음부터 끝까지 탐색하기 때문에 O(n)의 시간이 걸립니다. 따라서 위 방식들은 전체적으로 O(n×m)의 복잡도를 가집니다. 배열 크기가 수천 건 이상으로 크다면, 먼저 arr2를 Set으로 변환해 조회 비용을 O(1)로 줄이는 것이 좋습니다.

const set2 = new Set(arr2);
const rest = arr1.filter(el => !set2.has(el));
const ordered = arr2.filter(el => set1.has(el)); // set1 = new Set(arr1)
const result = [...ordered, ...rest];

이처럼 상황에 맞는 방법을 선택하면 가독성과 성능을 모두 챙길 수 있습니다.