어딘가 이상한 자바스크립트 문자열 배열 정렬 이야기
이 글은 문자열을 가진 배열을 정렬할 때 만날 수 있는 상황들 중 몇 가지를 살펴봅니다.
닉네임을 정렬해서 선물을 주는 온라인 세미나에서는..
15명이 참여하는 자바스크립트 개발자들을 위한 온라인 세미나를 열었다고 가정합니다. 참여한 모두에게 아래 조건의 결과에 따라 다른 현금을 선물로 준다고 합니다.
조건은 다음과 같이 알렸습니다
- 자바스크립트를 이용해서 정렬합니다.
- 어떤 문자를 사용해도 상관없습니다
- 닉네임을 기준으로 오름차순으로 정렬합니다.
참여한 사용자의 닉네임은 다음과 같습니다.
const 참여한사람 = [
'!import!',
'!중요!',
'?qa?',
'?질문?',
'Kore@',
'Korea',
'[Test]',
'[테스트]',
'kore@',
'korea',
'{test}',
'{테스트}',
'Übermensch',
'übermensch',
'한국'
];
결과는 어떻게 될까요? 모든 사용자가 납득할 수 있는 결과가 나왔을까요?
결과는 정렬 방법에 따라 다릅니다.
웹페이지에서 직접 결과를 보시려면 아래 URL을 눌러주세요
https://js-string-array-sort.netlify.app/
여기서 정렬은 모두 Array.prototype.sort()
메소드를 사용합니다.
- 비교 함수를 넣지 않고
sort()
- 숫자 처럼
sort((a, b) => a -b)
로 정렬 - 문자열을 비교하는 것 (>, <, =)으로 정렬
- localeCompare 기본 값으로 정렬
- localeCompare 대소문자 비교 옵션을 포함하여 정렬
- Intl.Collator 영어 조건으로 정렬
- Intl.Collator 한국어 조건으로 정렬
이 밖에 더 많은 정렬 방법이 있을 수 있겠지만 이번에는 위 조건까지만 알아봅니다.
1. 비교 함수를 넣지 않고 정렬
비교 함수를 sort
에 전달하지 않은 sort()
메소드를 호출하면 배열의 각 요소가 가지고 있는 한글자 마다의 유니코드 포인트 값을 이용하여 정렬합니다.
이미 위에 적혀있는 참여한사람
은 정렬 sort()
메소드를 이용하여 정렬된 상태입니다.
글자의 유니코드는 위키피디아 — List of Unicode characters에서 확인할 수 있습니다. 다 외울수는 없으니 다음 내용만 기억하고 있으면 될 것 같습니다.
모든 특수문자가 항상 낮은 유니코드 포인트 값을 가지고 있지는 않습니다.
ASCII로 표현되는 영어가 한글보다 낮은 유니코드 포인트 값을 가지고 있습니다.
2. 숫자 처럼 -
연산자를 사용해서 정렬
문자열 - 문자열
의 연산 결과는 무엇일까요? NaN
이라고 생각하셨으면 이번 참여자 목록에서는 정답입니다.
하지만 어떤 사용자가 ‘3’을, 또 다른 사용자가 ‘1’ 을 닉네임으로 사용하면 어떻게 될까요? 정렬 도중에 만나는 숫자만 포함한 문자열은 뺄셈 연산자를 만나면 숫자처럼 바뀝니다.
이 이유 때문에 모두가 숫자를 사용한다면 정상적으로 정렬이 되겠지만, 누군가 문자열을 사용한다면 NaN
때문에 배열이 정렬되지 않습니다.
['a', '3', 'b', '1'].sort((a, b) => a - b)
// 결과는 ['a', '3', 'b', '1']
문자열은 숫자와 같은 뺄셈 연산자를 사용하면 예상치 못한 결과를 만날 수 있습니다.
3. 비교 연산자 >, <, ===
로 정렬
비교 연산자는 비교 함수를 넣지 않고 정렬한 결과와 같습니다. 오름차순 정렬을 하는 경우에는 sort()
메소드를 사용하면 됩니다.
4, 5. localeCompare 을 사용하여 정렬 (기본값, 대소문자 비교)
localeCompare
는 정렬하려는 문자열의 로케일과 함께 여러가지 옵션을 제공하는 String
객체의 내장 함수입니다.
기본적으로 a.localeCompare(b)
와 같이 사용합니다. Array.prototype.sort()
문서에는 ASCII 문자열이 아닌 경우에 사용하라고 적혀있으나, 사전적인 순서로 정렬할 필요가 있다면 localeCompare
가 적절한 선택일 수 있습니다.
String.prototype.localeCompare
에서 소개하는 내용 중 몇가지만 기억하면 상황에 맞게 사용할 수 있을 것입니다.
String.prototype.localeCompare
의 결과는 -1, 1이 아니라 -2, 2 일 수도 있습니다.결과가 0 또는 양의 정수, 음의 정수라고 기억하세요.
문자열이 숫자인 경우 상황에 맞게
{ numeric: true }
옵션을 사용하세요.
사용하지 않으면 "2" > "10" 이고 사용한다면 "2" < "10" 입니다.정렬하려는 문자열이 특정 언어를 포함하고, 언어를 기준으로 정렬하려면 두번째 파라미터에 locale 을 적어주세요. 영어라면 'en' 을, 한국어는 'ko' 입니다.
Intl.Collator 를 사용하여 정렬 (영어, 한국어)
Intl.Collator
는 설정한 언어를 기준으로 고성능으로 비교하는 클래스입니다. 이번과 같이 작은 크기의 배열에서는 항상 그렇지는 않았습니다.
정확히는 Intl.Collator
클래스로 만든 인스턴스 메소드인 compare
를 사용합니다. 원하는 언어와 caseFirst
를 지정할 수 있습니다.
위 영상에서는 en
과 ko
만 지정해서 비교해보았습니다. 설정한 언어가 오름차순의 경우 더 앞에 오게 됩니다.
localeCompare
의 결과와는 조금 다른데, localeCompare
에 언어 설정을 하지않고, Intl.Collator
에 영어 옵션을 설정하면 localeCompare
는 한글이 더 앞으로, Intl.Collator
는 영어가 더 앞 순위를 차지합니다. Intl.Collator
에 영어 대신 한국어 옵션을 넣는다면 동일한 결과가 나옵니다. 그리고 아무것도 설정하지 않으면 localeCompare
의 결과와 같습니다.
localeCompare 와 Intl.Collator 는 언어를 설정하면 같은 결과를 보여줍니다.
MDN에는 성능을 위해 많은 문자열을 정렬할 때는
Intl.Collator
를 사용하라고 하지만, 아무 조건이 없는 localeCompare는 Intl.Collator보다 빠릅니다.localeCompare 와 Intl.Collator 는 언어 설정에 따라 매우 다른 결과를 보여줍니다.
마치며
보통 개발을 하면서 페이징을 하거나 웹 앱 개발을 할 때 서버에서 내려온 (특히 대부분 날짜 정렬된 경우) 결과는 데이터베이스의 정렬에 의존합니다.
클라이언트에서 정렬되지 않은 데이터를 문자열을 기준으로 정렬해서 페이징을 해야하는 경우가 있다면 옵션과 정렬해야하는 문자열의 종류에 따라 너무나 다양한 결과가 나올 수 있습니다.
그리고 MongoDB의 문자열 기본 정렬은 자바스크립트의 sort()
와 같았습니다.