排序:为什么 sort 把 Z 排在 a 前
["Z","a"].sort() 得到的还是 ["Z","a"]:默认的 sort() 逐个比较 UTF-16 码元值,而大写字母的码点全在小写之前,带音标的字母又被甩到 ASCII 之后,"file10" 还会排在
"file9" 前面。按某种语言的字典序排是另一回事,由 Unicode Collation Algorithm 定义,JS 里走 Intl.Collator。与 拼音 同理:排序规则不写在码点里,是另一份按 locale 调的权重表。
1 · 码元序不是字母序
把同一组词分别交给 sort() 与 Intl.Collator 排,差别一眼可见:码元序里大写聚一堆、小写聚一堆、带音标的垫底,不是任何人读得下去的顺序。中文传 'zh' 会按拼音排,这也印证了排序规则在码点之外。
2 · 权重的三层
UCA 给每个字符算多级权重:primary 管基字母(a 与 b),secondary 管变音(a 与 á),tertiary 管大小写(a 与 A)。Intl.Collator 的 sensitivity 就是「比到第几层」,比得越浅,越多字符被视作相等。'base'
下 a 与 á、a 与 A 都相等,'accent' 下音标开始区分而大小写仍不分,'variant'(默认)三层全比。
注 · 不分大小写与音标的搜索用 sensitivity: 'base',区分音标但不分大小写用 'accent',完整排序用默认的 'variant'。注意 compare(a, b) === 0 只表示「排序意义上相等」,不等于 ===;若还担心同字多形,比较前先
归一化。
3 · locale 的差别
同一组字母,不同语言排出的顺序不同。实测 a z ä ö å 这组:英语与德语都排成 a å ä ö z——它们把音标当次级差异,ä 紧挨着 a;瑞典语排成 a z å ä ö,把 å ä ö 当独立字母放到字母表末尾。同一份数据、同一个 API,只因 locale
不同就给出两种顺序。
4 · 数字的比法
字符串里的数字默认按字符比,'1' 小于 '9',于是 "file10" 排在 "file9" 前面。Intl.Collator 开 numeric: true 会把连续数字当数值比,这正是文件管理器里「自然排序」的做法。
建议 · 凡是给人看的排序,或大小写与音标无关的查找,都别用裸 sort() 或 <,那是码元序。用 Intl.Collator(locale, { sensitivity, numeric }),把「按哪种语言、忽略到哪层、数字怎么比」交给标准。中文传 'zh' 即按拼音排。
5 · 参考文献
- Unicode. UTS #10: Unicode Collation Algorithm. 多级权重的定义、默认排序元素表与各级比较规则。unicode.org
- Unicode. CLDR Collation Charts. 各 locale 对默认权重表的定制排出的实际字母序,瑞典语把
å ä ö放到字母表末尾即见于此(规则源文件为 CLDR 的common/collation/sv.xml)。unicode.org - Ecma International. ECMA-402: Intl.Collator.
sensitivity、numeric、caseFirst等选项的语义。tc39.es - MDN. Intl.Collator. 常见用法与
compare作为sort比较器的写法。developer.mozilla.org