본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev18

한글 이름을 sort()로 정렬했더니 순서가 이상했던 이유

  • #Engineering Note
  • #i18n
  • #JavaScript

문제 발생

이름 목록을 정렬했는데 사전 순서와 달랐고, 숫자가 섞인 파일명은 더 이상하게 나왔습니다.

["파일10", "파일2", "파일1"].sort();
// ['파일1', '파일10', '파일2']   — 10이 2보다 앞

원인 분석

인자 없는 Array.prototype.sort()는 원소를 문자열로 바꾼 뒤 유니코드 코드 포인트 순서로 비교합니다. 언어의 정렬 규칙을 전혀 모릅니다.

그래서 두 종류의 문제가 생깁니다.

언어별 규칙. 독일어에서 äa의 변형으로 취급되지만 스웨덴어에서는 z 뒤에 옵니다. 코드 포인트 순서는 둘 다 표현하지 못합니다.

new Intl.Collator("de").compare("ä", "z");   // -1
new Intl.Collator("sv").compare("ä", "z");   //  1

숫자가 섞인 문자열. "파일10"과 "파일2"는 문자 단위로 비교되므로 1 < 2에서 순서가 정해집니다.

Intl.Collator는 언어별 정렬 규칙(CLDR)을 담고 있어 두 문제를 모두 다룹니다.

해결 방안

  1. 비교 함수로 Collator를 넘깁니다.
const collator = new Intl.Collator("ko");
names.sort(collator.compare);
  1. 숫자를 숫자로 비교하려면 numeric 옵션을 켭니다.
const natural = new Intl.Collator("ko", { numeric: true });
["파일10", "파일2", "파일1"].sort(natural.compare);
// ['파일1', '파일2', '파일10']
  1. Collator를 만들어 재사용합니다. MDN이 명시하는 성능 권고입니다 — 큰 배열을 정렬할 때 localeCompare를 매번 호출하면 비교마다 인스턴스 생성 비용이 듭니다.
// 권장
const collator = new Intl.Collator("ko");
array.sort(collator.compare);

// 비교마다 인스턴스가 만들어진다
array.sort((a, b) => a.localeCompare(b, "ko"));
  1. 검색·중복 판정에는 sensitivity를 조절합니다. 대소문자나 악센트를 무시하고 같은 것으로 보려면 baseaccent를 씁니다.
new Intl.Collator("ko", { sensitivity: "base" }).compare("Cafe", "café");   // 0
  1. DB 정렬과 화면 정렬을 섞지 않습니다. 목록을 DB에서 ORDER BY로 정렬해 내려주면 그 순서는 DB의 콜레이션을 따릅니다. 클라이언트에서 다시 정렬하면 두 규칙이 충돌해 페이지마다 순서가 달라 보일 수 있습니다. 한쪽에서만 정렬합니다.
  2. 반환값은 부호만 보장됩니다. 명세가 정하는 것은 음수·0·양수뿐이고 구체적인 값은 구현마다 다를 수 있으므로, === -1 같은 비교를 하지 않습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.