문자열과 숫자가 섞여 있는 데이터를 정렬할 때는 단순히 sort()만 호출해서는 원하는 결과를 얻기 어렵습니다. 기본 정렬은 사전식(lexicographic) 비교를 수행하기 때문에 'ab-10'이 'ab-2'보다 앞에 오는 등 직관적이지 않은 순서가 나올 수 있습니다.
예를 들어 다음과 같이 알파벳 부분과 숫자 부분이 결합된 배열이 있다고 가정해 보겠습니다. 이 배열을 먼저 알파벳 순으로, 그다음에는 숫자 크기 순으로 정렬하려고 합니다.
const arr = ['Ab-1', 'Ab-11', 'Ab-12', 'ab-10', 'ab-100', 'ab-101', 'ab-2', 'ab-3', 'ab-105'];
영숫자 정렬 구현 예제
핵심 아이디어는 각 문자열을 하이픈(-)을 기준으로 나눈 뒤, 첫 번째 조각은 대소문자를 통일하여 localeCompare()로 비교하고, 두 번째 조각은 숫자로 간주해 차감 비교하는 것입니다. localeCompare()의 결과가 0이면(즉, 알파벳 부분이 같으면) || 연산자 덕분에 이어서 숫자 비교가 수행됩니다.
const arr = ['Ab-1', 'Ab-11', 'Ab-12', 'ab-10', 'ab-100', 'ab-101', 'ab-2', 'ab-3', 'ab-105'];
const alphaNumericSort = (arr = []) => {
arr.sort((a, b) => {
const aPart = a.split('-');
const bPart = b.split('-');
return aPart[0].toLowerCase().localeCompare(bPart[0].toLowerCase()) || aPart[1] - bPart[1];
});
};
alphaNumericSort(arr);
console.log(arr);
코드 동작 원리
- split('-'): 문자열을 하이픈 기준으로 분리해 ['알파벳 부분', '숫자 부분'] 형태의 배열을 만듭니다.
- toLowerCase() + localeCompare(): 'Ab'와 'ab'처럼 대소문자가 달라도 같은 값으로 취급해 알파벳 순서를 비교합니다.
- aPart[1] - bPart[1]: 산술 연산 과정에서 자동으로 숫자 변환이 일어나므로 '10'과 '2'를 문자열이 아닌 숫자로 올바르게 비교합니다.
실행 결과
콘솔 출력은 다음과 같습니다.
[ 'Ab-1', 'ab-2', 'ab-3', 'ab-10', 'Ab-11', 'Ab-12', 'ab-100', 'ab-101', 'ab-105' ]
출력을 보면 알파벳 접두사가 같은 항목끼리 묶인 상태에서 숫자가 오름차순으로 배치된 것을 확인할 수 있습니다. 즉, 'ab-10'이 'ab-2'보다 뒤에 오는 자연스러운(natural) 정렬 순서가 완성됩니다.
참고: localeCompare의 numeric 옵션 활용
모던 자바스크립트 환경에서는 localeCompare()에 { numeric: true } 옵션을 지정하는 더 간단한 방법도 있습니다. 이 옵션을 사용하면 문자열 안의 숫자를 실제 숫자 값처럼 취급합니다.
const sorted = [...arr].sort((a, b) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
);
console.log(sorted);
sensitivity: 'base'는 대소문자 차이를 무시한 비교를 의미합니다. 두 가지 방법 모두 파일 이름, 버전 문자열, 제품 코드처럼 영숫자가 섞인 데이터를 정렬할 때 유용하게 활용할 수 있습니다.