어딘가 이상한 자바스크립트 문자열 배열 정렬 이야기

직접 테스트할 수 있는 URL은 아래에 있습니다 🎿

이 글은 문자열을 가진 배열을 정렬할 때 만날 수 있는 상황들 중 몇 가지를 살펴봅니다.

닉네임을 정렬해서 선물을 주는 온라인 세미나에서는..

조건은 다음과 같이 알렸습니다

  • 자바스크립트를 이용해서 정렬합니다.
  • 어떤 문자를 사용해도 상관없습니다
  • 닉네임을 기준으로 오름차순으로 정렬합니다.

참여한 사용자의 닉네임은 다음과 같습니다.

const 참여한사람 = [
'!import!',
'!중요!',
'?qa?',
'?질문?',
'Kore@',
'Korea',
'[Test]',
'[테스트]',
'kore@',
'korea',
'{test}',
'{테스트}',
'Übermensch',
'übermensch',
'한국'
];

결과는 어떻게 될까요? 모든 사용자가 납득할 수 있는 결과가 나왔을까요?

결과는 정렬 방법에 따라 다릅니다.

https://js-string-array-sort.netlify.app/

여기서 정렬은 모두 Array.prototype.sort() 메소드를 사용합니다.

  1. 비교 함수를 넣지 않고 sort()
  2. 숫자 처럼 sort((a, b) => a -b) 로 정렬
  3. 문자열을 비교하는 것 (>, <, =)으로 정렬
  4. localeCompare 기본 값으로 정렬
  5. localeCompare 대소문자 비교 옵션을 포함하여 정렬
  6. Intl.Collator 영어 조건으로 정렬
  7. Intl.Collator 한국어 조건으로 정렬

이 밖에 더 많은 정렬 방법이 있을 수 있겠지만 이번에는 위 조건까지만 알아봅니다.

1. 비교 함수를 넣지 않고 정렬

이미 위에 적혀있는 참여한사람 은 정렬 sort() 메소드를 이용하여 정렬된 상태입니다.

글자의 유니코드는 위키피디아 — List of Unicode characters에서 확인할 수 있습니다. 다 외울수는 없으니 다음 내용만 기억하고 있으면 될 것 같습니다.

모든 특수문자가 항상 낮은 유니코드 포인트 값을 가지고 있지는 않습니다.

ASCII로 표현되는 영어가 한글보다 낮은 유니코드 포인트 값을 가지고 있습니다.

2. 숫자 처럼 - 연산자를 사용해서 정렬

하지만 어떤 사용자가 ‘3’을, 또 다른 사용자가 ‘1’ 을 닉네임으로 사용하면 어떻게 될까요? 정렬 도중에 만나는 숫자만 포함한 문자열은 뺄셈 연산자를 만나면 숫자처럼 바뀝니다.

이 이유 때문에 모두가 숫자를 사용한다면 정상적으로 정렬이 되겠지만, 누군가 문자열을 사용한다면 NaN 때문에 배열이 정렬되지 않습니다.

['a', '3', 'b', '1'].sort((a, b) => a - b) 
// 결과는 ['a', '3', 'b', '1']

문자열은 숫자와 같은 뺄셈 연산자를 사용하면 예상치 못한 결과를 만날 수 있습니다.

3. 비교 연산자 >, <, === 로 정렬

4, 5. localeCompare 을 사용하여 정렬 (기본값, 대소문자 비교)

기본적으로 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 클래스로 만든 인스턴스 메소드인 compare 를 사용합니다. 원하는 언어와 caseFirst 를 지정할 수 있습니다.

위 영상에서는 enko 만 지정해서 비교해보았습니다. 설정한 언어가 오름차순의 경우 더 앞에 오게 됩니다.

localeCompare 의 결과와는 조금 다른데, localeCompare 에 언어 설정을 하지않고, Intl.Collator 에 영어 옵션을 설정하면 localeCompare 는 한글이 더 앞으로, Intl.Collator 는 영어가 더 앞 순위를 차지합니다. Intl.Collator 에 영어 대신 한국어 옵션을 넣는다면 동일한 결과가 나옵니다. 그리고 아무것도 설정하지 않으면 localeCompare 의 결과와 같습니다.

localeCompare 와 Intl.Collator 는 언어를 설정하면 같은 결과를 보여줍니다.

MDN에는 성능을 위해 많은 문자열을 정렬할 때는 Intl.Collator 를 사용하라고 하지만, 아무 조건이 없는 localeCompare는 Intl.Collator보다 빠릅니다.

localeCompare 와 Intl.Collator 는 언어 설정에 따라 매우 다른 결과를 보여줍니다.

마치며

클라이언트에서 정렬되지 않은 데이터를 문자열을 기준으로 정렬해서 페이징을 해야하는 경우가 있다면 옵션과 정렬해야하는 문자열의 종류에 따라 너무나 다양한 결과가 나올 수 있습니다.

그리고 MongoDB의 문자열 기본 정렬은 자바스크립트의 sort() 와 같았습니다.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store