一、一個具體的例子引發的問題
當今是國際化的時代,多種語言可能同時顯示在屏幕上。比如一個人可能喜歡聽華語歌、英文歌、韓文歌和日語歌,又比如他的聯系人中有中國人、英國人、日本人、韓國人以及有英文名字的中國人。
在這種情況下,他的手機上需要維護一個列表,每一項可能是中文、韓文、英文和日文。在許多情況下需要對這個列表進行維護,那么如何對這些表項進行排序呢?為了解決這個問題,至少要回答以下幾個問題:
- 中文、韓文、英文、日文的順序是怎樣的呢?那個語種在前面呢?
- 同一個語種內部如何排序呢?比如中文,是按照漢語拼音還是部首,抑或是筆畫數排序呢?
- 對於數字、標點符號、特殊符號等獨立於某一個書寫系統的字符 (character),如何處理呢?
二、事實上的排序標准: UCA+CLDR
使用這種方法來排序的公司和組織有 Apple、Google、IBM、MicroSoft、Amazon、Python、Wikimedia Foundation (Wikipedia)、Apache...
在蘋果的文檔中,可以找到下面的描述:
Localized string comparisons are based on the Unicode Collation Algorithm, as tailored for different languages by CLDR (Common Locale Data Repository).
-
UCA
首先解釋一下 Collation 的含義。Collation is the general term for the process and function of determining the sorting order of strings of characters.
如果有若干字符串需要排序,確定排序的過程就是 Collation,可以認為是排序 (sort) 在字符串領域的特化。
Unicode Collation Algorithm (UCA) 是 Unicode 制定的如何比較兩個 Unicode 字符串的規范。注意這里是字符串,而不僅僅是字符。
UCA 的規則很復雜,我們以后再說。但從名字上可以看出,UCA 只是一個算法,算法需要數據才能產出結果。
UCA 最后產出了一個文檔,指定了默認情況下 Unicode 字符的順序。但是這僅僅是默認情況,也就是照顧了大多數情況(也就是排序對英語國家比較友好。。。)。對於其他地區的人們來說,就需要輸入和默認情況不同的數據,以獲得和當地習慣相符合的結果。比如同樣的漢字,在中國大陸是按照漢語拼音排序的,在香港就是按照筆畫數目排序的。 -
CLDR
Common Locale Data Repository (CLDR),從名字上可以看出,這個實際上是一堆數據的倉庫。對於指定的地區 (locale),可以從中找到指定的數據。再結合 UCA,就可以得到符合當期習慣的排序結果。
三、UCA 默認順序
UCA 最后產出了一個文檔,在common/uca/allkeys_CLDR.txt
。這個文檔就指定了默認情況下的 Unicode 字符的順序。
0031 ; [.1B3F.0020.0002] # DIGIT ONE
0661 ; [.1B3F.0020.0002] # ARABIC-INDIC DIGIT ONE
這個文檔中的每一行都有上面的格式。分號;
之前的部分是 Unicode 字符對應的 Unicode 碼點 (code point)。分號之后是用於 UCA 算法的權重,用.
分隔。#
及之后部分是注釋。
所有的 Unicode 字符從上到下依次排序。
3.1 Unicode 字符分類
Unicode 把所有字符分為兩類,並按順序排列:
- common characters
包括空格、標點、通用符號、貨幣符號和數字。 - script characters
包括拉丁字母、希臘字母、漢字等。
把字符分類,便於把某一類字符統統放到另一類字符之前,比如把漢字放在英文之前。
注意:這里默認排序並不是按照 Unicode 碼點順序依次排列!
3.2 利用 UCA 默認值排序的例子
rawArray = @[@"cc",@"曹操",@"bb",@"1",@"١",@"(en",@"(zh"];
NSArray *sortedArray = [rawArray sortedArrayUsingComparator:^NSComparisonResult(NSString * _Nonnull obj1, NSString * _Nonnull obj2) {
return [obj1 compare:obj2 options:NSCaseInsensitiveSearch];
}];
__block NSMutableArray *codeUnits = [NSMutableArray array];
[sortedArray enumerateObjectsUsingBlock:^(NSString* _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[codeUnits addObject:@([obj characterAtIndex:0])];
}];
NSLog(@"after default sort , result is %@, codeUnits is %@",sortedArray,codeUnits);
NSLocale *locale=[[NSLocale alloc] initWithLocaleIdentifier:@"en"];
sortedArray = [rawArray sortedArrayUsingComparator:^NSComparisonResult(NSString * _Nonnull obj1, NSString * _Nonnull obj2) {
NSRange string1Range = NSMakeRange(0, [obj1 length]);
return [obj1 compare:obj2 options:0 range:string1Range locale:locale];
}];
NSLog(@"after locale sort , result is %@",sortedArray);
輸出結果如下:
after default sort , result is ((en,1,bb,cc,١,曹操,(zh,), codeUnits is (40,49,98,99,1633,26361,65288,)
after locale sort , result is ((en,(zh,1,١,bb,cc,曹操,)
未指定地區信息時,NSString
的排序函數僅僅是按照UTF-16
code unit 的值排列的。在指定地區信息是en
后,按照allkeys_CLDR.txt
中指定的順序,也更加符合人們的預期。依次是標點符號(英文和中文的左括號)、數字(阿拉伯數字 1 和另一種表示方法)、scripts(英文和中文)
四、CLDR 對排序結果進行調整 (tailor)
如上圖,CLDR 最新版本的數據有很多文件夾,都是用一種標記語言 (LDML) 來書寫的。我們一步一步來確定如何決定在中國大陸對 Unicode 字符進行排序。
4.1 確定采用何種排序規則
在bcp47/collation.xml
中,指出了可選的很多種排序方式,包括standard
、pinyin
、big5han
、stroke
(筆畫排序)。
那么在中國大陸應該采用哪種排序方法呢?可以在collation/zh.xml
中找到指定的排序方式是pinyin
。而繁體中文的默認排序方式是stroke
。
<defaultCollation>pinyin</defaultCollation> in collation/zh.xml
<defaultCollation>stroke</defaultCollation> in collation/zh_Hant.xml
4.2 確定漢語內部的排序規則
在collation/zh.xml
中可以看到下圖。和程序中的頭文件一樣,pinyin
排序規則引入了private-pinyin
這個規則,此外,還引入了默認的規則。即如果兩個字符(如阿拉伯數字 1 和 2 )在pinyin
規則中找不到依據,那么根據默認規則進行排序。這樣子做大大降低了維護成本,在原理上和 "Don't repeat yourself" 類似。
我們現在來看具體的排序規則。
- 在
private-pinyin
中,指定了可以標“聲調”的字母在各種聲調情況下的排列順序。
你沒有看錯,m
和n
也可以標聲調! - 指定了拼音的順序以及同音字的順序
首先按照拼音排序,表現為不同行之間的順序。對於同音字,也就是每一行之間的順序,先按照筆畫數排序,再按照kRSUnicode
排序。
請注意,在ā
之前有一行,是用來構建索引的,即此行之下直至另一個索引都屬於A
的索引之內。
4.3 不同語種字母的順序
當指定了地區之后,這個地區的字符將會在所有的 “script characters” 中排列第一。其他地區的字符按照默認順序排序。
- 確定當地的書寫系統 (scripts)
在./main/zh_Hans_CN.xml
中,看到<script type="Hans"/>
- 確定書寫系統包括哪些字符
在./uca/FractionalUCA.txt
中,搜索first primary
,得到下圖:
在此行之下,即是所有漢字。第一個就是康熙字典的“一”,第二個是“㊀”。 - 其他書寫系統的字符的順序
依據默認的順序,即在common/uca/allkeys_CLDR.txt
規定的順序。
4.4 一個例子
對於以下的字符串數組排序:
rawArray = @[@"上",@"㊤",@"μ",@"язык"];
這個字符數組中,包括漢語、希臘語和俄語。指定不同的地區之后,得到不同的結果。
locale is el_CN, result is (µ,язык,上,㊤,) //希臘語
locale is ru_CN, result is (язык,µ,上,㊤,) //俄語
locale is zh_CN, result is (上,㊤,µ,язык,) //中國
locale is en, result is (µ,язык,上,㊤,) //英語