숫자가 포함된 문자열을 올바르게 정렬하는 방법
숫자 콜레이션을 사용하여 파일 이름, 버전 번호 및 숫자가 포함된 기타 문자열을 자연스러운 순서로 정렬하세요
소개
숫자가 포함된 문자열을 정렬할 때 file1.txt, file2.txt, file10.txt가 해당 순서로 나타나기를 기대합니다. 그러나 JavaScript의 기본 문자열 비교는 대신 file1.txt, file10.txt, file2.txt를 생성합니다. 이는 문자열이 문자별로 비교되고 10의 문자 1가 문자 2보다 앞에 오기 때문에 발생합니다.
이 문제는 파일 이름, 버전 번호, 주소, 제품 코드 또는 숫자가 포함된 기타 문자열을 정렬할 때마다 나타납니다. 잘못된 순서는 사용자를 혼란스럽게 하고 데이터 탐색을 어렵게 만듭니다.
JavaScript는 이 문제를 해결하는 숫자 옵션이 있는 Intl.Collator API를 제공합니다. 이 레슨에서는 숫자 콜레이션이 작동하는 방식, 기본 문자열 비교가 실패하는 이유, 그리고 숫자가 포함된 문자열을 자연스러운 숫자 순서로 정렬하는 방법을 설명합니다.
숫자 콜레이션이란
숫자 콜레이션은 숫자 시퀀스를 개별 문자가 아닌 숫자로 취급하는 비교 방법입니다. 문자열을 비교할 때 콜레이터는 숫자 시퀀스를 식별하고 숫자 값으로 비교합니다.
숫자 콜레이션이 비활성화된 경우 문자열 file10.txt는 file2.txt보다 앞에 옵니다. 문자별 비교에서 첫 번째 차이 위치에서 1가 2보다 앞에 오기 때문입니다. 콜레이터는 10가 2보다 큰 숫자를 나타낸다는 것을 고려하지 않습니다.
숫자 콜레이션이 활성화된 경우 콜레이터는 10와 2를 완전한 숫자로 인식하고 숫자로 비교합니다. 10이 2보다 크므로 file2.txt가 file10.txt보다 올바르게 앞에 옵니다.
이 동작은 사람들이 자연 정렬 또는 자연 순서라고 부르는 것을 생성하며, 숫자를 포함한 문자열이 엄격하게 알파벳순이 아닌 사람이 예상하는 방식으로 정렬됩니다.
숫자에 대한 기본 문자열 비교가 실패하는 이유
JavaScript의 기본 문자열 비교는 사전식 순서를 사용하며, 유니코드 코드 포인트 값을 사용하여 왼쪽에서 오른쪽으로 문자를 하나씩 비교합니다. 이는 알파벳 텍스트에는 올바르게 작동하지만 숫자에는 예상치 못한 결과를 생성합니다.
사전식 비교가 이러한 문자열을 처리하는 방법을 고려해 보세요.
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
files.sort();
console.log(files);
// Output: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
비교는 각 문자 위치를 독립적으로 검사합니다. file 이후 첫 번째로 다른 위치에서 1와 2를 비교합니다. 1가 2보다 낮은 유니코드 값을 가지므로, file1로 시작하는 모든 문자열은 뒤에 무엇이 오든 상관없이 file2로 시작하는 모든 문자열보다 앞에 옵니다.
이는 file1.txt, file10.txt, file2.txt, file20.txt 순서를 생성하며, 이는 숫자 순서에 대한 인간의 기대를 위반합니다.
numeric 옵션과 함께 Intl.Collator 사용하기
Intl.Collator 생성자는 numeric 속성이 있는 옵션 객체를 받습니다. numeric: true를 설정하면 숫자 대조가 활성화되어 콜레이터가 숫자 시퀀스를 숫자 값으로 비교하게 됩니다.
const collator = new Intl.Collator('en-US', { numeric: true });
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
files.sort(collator.compare);
console.log(files);
// Output: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']
콜레이터의 compare 메서드는 첫 번째 인수가 두 번째 인수보다 앞에 와야 할 때 음수를, 같을 때 0을, 첫 번째가 뒤에 와야 할 때 양수를 반환합니다. 이는 JavaScript의 Array.sort() 메서드가 예상하는 시그니처와 일치합니다.
정렬된 결과는 파일을 자연스러운 숫자 순서로 배치합니다. 콜레이터는 1 < 2 < 10 < 20임을 인식하여 사람이 예상하는 순서를 생성합니다.
혼합 영숫자 문자열 정렬
숫자 콜레이션은 끝부분뿐만 아니라 어느 위치에나 숫자가 나타나는 문자열을 처리합니다. 콜레이터는 알파벳 부분은 일반적으로 비교하고 숫자 부분은 숫자로 비교합니다.
const collator = new Intl.Collator('en-US', { numeric: true });
const addresses = ['123 Oak St', '45 Oak St', '1234 Oak St', '5 Oak St'];
addresses.sort(collator.compare);
console.log(addresses);
// Output: ['5 Oak St', '45 Oak St', '123 Oak St', '1234 Oak St']
콜레이터는 각 문자열의 시작 부분에 있는 숫자 시퀀스를 식별하고 숫자로 비교합니다. 사전식 비교는 다른 순서를 생성하지만, 5 < 45 < 123 < 1234임을 인식합니다.
버전 번호 정렬
버전 번호는 숫자 콜레이션의 일반적인 사용 사례입니다. 1.2.10와 같은 소프트웨어 버전은 1.2.2 다음에 와야 하지만, 사전식 비교는 잘못된 순서를 생성합니다.
const collator = new Intl.Collator('en-US', { numeric: true });
const versions = ['1.2.10', '1.2.2', '1.10.5', '1.2.5'];
versions.sort(collator.compare);
console.log(versions);
// Output: ['1.2.2', '1.2.5', '1.2.10', '1.10.5']
콜레이터는 각 숫자 구성 요소를 올바르게 비교합니다. 1.2.2, 1.2.5, 1.2.10 시퀀스에서 세 번째 구성 요소가 숫자로 증가함을 인식합니다. 1.10.5에서는 두 번째 구성 요소가 10이며, 이는 2보다 크다는 것을 인식합니다.
제품 코드 및 식별자 작업
제품 코드, 송장 번호 및 기타 식별자는 종종 문자와 숫자를 혼합합니다. 숫자 콜레이션은 이러한 항목이 논리적 순서로 정렬되도록 보장합니다.
const collator = new Intl.Collator('en-US', { numeric: true });
const products = ['PROD-1', 'PROD-10', 'PROD-2', 'PROD-100'];
products.sort(collator.compare);
console.log(products);
// Output: ['PROD-1', 'PROD-2', 'PROD-10', 'PROD-100']
알파벳 접두사 PROD-는 모든 문자열에서 일치하므로 콜레이터는 숫자 접미사를 비교합니다. 결과는 사전식 순서가 아닌 증가하는 숫자 순서를 반영합니다.
다른 로케일로 정렬
numeric 옵션은 모든 로케일에서 작동합니다. 로케일마다 알파벳 문자에 대한 정렬 규칙이 다를 수 있지만, 숫자 비교 동작은 일관되게 유지됩니다.
const enCollator = new Intl.Collator('en-US', { numeric: true });
const deCollator = new Intl.Collator('de-DE', { numeric: true });
const items = ['item1', 'item10', 'item2'];
console.log(items.sort(enCollator.compare));
// Output: ['item1', 'item2', 'item10']
console.log(items.sort(deCollator.compare));
// Output: ['item1', 'item2', 'item10']
문자열에 ASCII 문자와 숫자만 포함되어 있기 때문에 두 로케일 모두 동일한 결과를 생성합니다. 문자열에 로케일별 문자가 포함된 경우 알파벳 비교는 로케일 규칙을 따르지만 숫자 비교는 일관되게 유지됩니다.
정렬 없이 문자열 비교하기
콜레이터의 compare 메서드를 직접 사용하여 전체 배열을 정렬하지 않고도 두 문자열 간의 관계를 확인할 수 있습니다.
const collator = new Intl.Collator('en-US', { numeric: true });
console.log(collator.compare('file2.txt', 'file10.txt'));
// Output: -1 (negative number means first argument comes before second)
console.log(collator.compare('file10.txt', 'file2.txt'));
// Output: 1 (positive number means first argument comes after second)
console.log(collator.compare('file2.txt', 'file2.txt'));
// Output: 0 (zero means arguments are equal)
이는 정렬된 목록에 항목을 삽입하거나 값이 범위 내에 있는지 확인하는 등 배열을 수정하지 않고 순서를 확인해야 할 때 유용합니다.
소수점 숫자의 제한 사항 이해하기
숫자 콜레이션은 숫자 시퀀스를 비교하지만 소수점을 숫자의 일부로 인식하지 않습니다. 마침표 문자는 소수점 구분 기호가 아닌 구분 기호로 취급됩니다.
const collator = new Intl.Collator('en-US', { numeric: true });
const measurements = ['0.5', '0.05', '0.005'];
measurements.sort(collator.compare);
console.log(measurements);
// Output: ['0.005', '0.05', '0.5']
콜레이터는 각 측정값을 세 개의 별도 숫자 구성 요소로 취급합니다: 마침표 앞 부분, 마침표 자체, 마침표 뒤 부분. 0와 0를 비교하고(동일), 그 다음 마침표 뒤의 부분을 별도의 숫자로 비교합니다: 5, 5, 5(동일). 그런 다음 두 번째 소수 자리를 비교합니다: 없음, 5, 없음. 이는 소수점 숫자에 대해 잘못된 순서를 생성합니다.
소수점 숫자를 정렬하려면 실제 숫자로 변환하여 숫자적으로 정렬하거나 문자열 패딩을 사용하여 올바른 사전식 순서를 보장해야 합니다.
숫자 콜레이션과 다른 옵션 결합하기
numeric 옵션은 sensitivity 및 caseFirst와 같은 다른 콜레이션 옵션과 함께 작동합니다. 숫자 비교 동작을 유지하면서 콜레이터가 대소문자와 악센트를 처리하는 방식을 제어할 수 있습니다.
const collator = new Intl.Collator('en-US', {
numeric: true,
sensitivity: 'base'
});
const items = ['Item1', 'item10', 'ITEM2'];
items.sort(collator.compare);
console.log(items);
// Output: ['Item1', 'ITEM2', 'item10']
sensitivity: 'base' 옵션은 비교를 대소문자 구분 없이 수행합니다. 콜레이터는 Item1, item1, ITEM1를 동등하게 취급하면서도 숫자 부분은 올바르게 비교합니다.
성능을 위한 collator 재사용
새로운 Intl.Collator 인스턴스를 생성하는 것은 로케일 데이터를 로드하고 옵션을 처리하는 작업을 포함합니다. 여러 배열을 정렬하거나 많은 비교를 수행해야 할 때는 collator를 한 번 생성하고 재사용하세요.
const collator = new Intl.Collator('en-US', { numeric: true });
const files = ['file1.txt', 'file10.txt', 'file2.txt'];
const versions = ['1.2.10', '1.2.2', '1.10.5'];
const products = ['PROD-1', 'PROD-10', 'PROD-2'];
files.sort(collator.compare);
versions.sort(collator.compare);
products.sort(collator.compare);
이 접근 방식은 각 정렬 작업마다 새로운 collator를 생성하는 것보다 더 효율적입니다. 성능 차이는 많은 배열을 정렬하거나 빈번한 비교를 수행할 때 더욱 두드러집니다.