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 ]
코드 동작 원리
- inBoth 함수:
includes()를 사용해 특정 요소가 arr1과 arr2 양쪽에 모두 존재하는지 판별합니다. - 비교 함수(sorter): sort()는 인접한 두 요소 a, b를 비교할 때 이 함수의 반환값을 기준으로 순서를 결정합니다.
- 음수 → a가 b보다 앞에 위치
- 양수 → b가 a보다 앞에 위치
- 0 → 순서 변경 없음
- 공통 요소 정렬: 두 요소가 모두 arr2에도 있다면
arr2.indexOf(a) - arr2.indexOf(b)로 arr2 내부의 인덱스 차이를 반환하여 참조 배열의 순서를 그대로 반영합니다. - 원본 보호:
[...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];
이처럼 상황에 맞는 방법을 선택하면 가독성과 성능을 모두 챙길 수 있습니다.