Unicode 字符串排序規則(一):如何確定單個字符的順序


一、一個具體的例子引發的問題

當今是國際化的時代,多種語言可能同時顯示在屏幕上。比如一個人可能喜歡聽華語歌、英文歌、韓文歌和日語歌,又比如他的聯系人中有中國人、英國人、日本人、韓國人以及有英文名字的中國人。
在這種情況下,他的手機上需要維護一個列表,每一項可能是中文、韓文、英文和日文。在許多情況下需要對這個列表進行維護,那么如何對這些表項進行排序呢?為了解決這個問題,至少要回答以下幾個問題:

  1. 中文、韓文、英文、日文的順序是怎樣的呢?那個語種在前面呢?
  2. 同一個語種內部如何排序呢?比如中文,是按照漢語拼音還是部首,抑或是筆畫數排序呢?
  3. 對於數字、標點符號、特殊符號等獨立於某一個書寫系統的字符 (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).

  1. 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 字符的順序。但是這僅僅是默認情況,也就是照顧了大多數情況(也就是排序對英語國家比較友好。。。)。對於其他地區的人們來說,就需要輸入和默認情況不同的數據,以獲得和當地習慣相符合的結果。比如同樣的漢字,在中國大陸是按照漢語拼音排序的,在香港就是按照筆畫數目排序的。

  2. 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 把所有字符分為兩類,並按順序排列:

  1. common characters
    包括空格、標點、通用符號、貨幣符號和數字。
  2. 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中,指出了可選的很多種排序方式,包括standardpinyinbig5hanstroke(筆畫排序)。

那么在中國大陸應該采用哪種排序方法呢?可以在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" 類似。
我們現在來看具體的排序規則。

  1. private-pinyin中,指定了可以標“聲調”的字母在各種聲調情況下的排列順序。
    你沒有看錯,mn也可以標聲調!
  2. 指定了拼音的順序以及同音字的順序
    首先按照拼音排序,表現為不同行之間的順序。對於同音字,也就是每一行之間的順序,先按照筆畫數排序,再按照kRSUnicode排序。
    請注意,在ā之前有一行,是用來構建索引的,即此行之下直至另一個索引都屬於A的索引之內。

4.3 不同語種字母的順序

當指定了地區之后,這個地區的字符將會在所有的 “script characters” 中排列第一。其他地區的字符按照默認順序排序。

  1. 確定當地的書寫系統 (scripts)
    ./main/zh_Hans_CN.xml中,看到<script type="Hans"/>
  2. 確定書寫系統包括哪些字符
    ./uca/FractionalUCA.txt中,搜索first primary,得到下圖:
    在此行之下,即是所有漢字。第一個就是康熙字典的“一”,第二個是“㊀”。
  3. 其他書寫系統的字符的順序
    依據默認的順序,即在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 (µ,язык,上,㊤,)        //英語

五、參考資料

  1. UNICODE COLLATION ALGORITHM
  2. CLDR
  3. CLDR 30.0.3
  4. UNICODE LOCALE DATA MARKUP LANGUAGE
  5. UNICODE HAN DATABASE (UNIHAN)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM