提供了在不同區域性下對字元範圍做批量大小寫轉換的方法,避免遍歷字元範圍中的每個字元。 ...
轉換字元的大小寫是一個很常用的功能,例如 char.ToUpper
、char.ToLower
或者 TextInfo.ToUpper
和 TextInfo.ToLower
。這些 API 都只操作單個字元,如果遇到了一個字元範圍,是需要遍歷範圍內的每個字元並依次執行轉換。
以從大寫字元轉換為小寫字元為例。對於一個字元範圍 A-Z
,可以很容易的分辨出相應的小寫字元範圍為 a-z
,這裡有幾個條件:
- 從
A-Z
到a-z
的轉換,可以通過字元值 + 32 計算得到。 A-Z
是連續的大寫字元範圍。
類似的,對於任意字元範圍,只要確認其中連續的大寫字元範圍,再將其起始和結束字元轉換為相應的小寫字元,就完成了轉換。
接下來,就是如何確認其中連續的大寫字元範圍了。系統正則表達式庫中的一個內部類 RegexCharClass
提供了一個方案,這裡定義了一個 LowerCaseMapping
數組,裡面按順序定義了所有大寫字元範圍和從大寫字元到小寫字元的轉換方式。部分示例如下所示:
new LowerCaseMapping('\u0041', '\u005A', LowercaseAdd, 32),
new LowerCaseMapping('\u00C0', '\u00DE', LowercaseAdd, 32),
new LowerCaseMapping('\u0100', '\u012E', LowercaseBor, 0),
new LowerCaseMapping('\u0130', '\u0130', LowercaseSet, 0x0069),
字元範圍可以直接在這個數組內二分得到,比較特別的就是如何從大寫字元轉換到相應的小寫字元。直接調用系統方法當然是一個方案,不過 RegexCharClass
的實現更為優化些。
根據統計,大小寫字元間的關係只有兩種:
- 將大寫字元加上/減去一個字元得到相應的小寫字元,例如
'A' + 32 = 'a'
。這種場景下大寫字元和小寫字元一般是互不覆蓋的。 - 將大寫字元加 1 得到相應的小寫字元,例如
'Ā' + 1 = 'ā'
。這種場景下大寫字元和小寫字元會依次出現,基本處於同一個範圍。
第一類關係很容易處理,LowercaseAdd
操作就是為當前字元加上一個固定的偏移量。只是會有一些特例,有時兩個連續大寫字元會對應同一個小寫字元,例如 DŽ (\u01C4)
和 Dž (\u01C5)
對應的小寫字元都是 dž (\u01C6)
,此時使用 LowercaseSet
操作來直接設置為指定的字元。
第二類操作會略微複雜。如果將每個大寫字元分別當作獨立的字元範圍,就會產生過多的零碎的字元範圍,非常不利於性能優化。而如果將大寫和小寫字元混合為同一個範圍,[Ā-ą]
,由於 ą + 1 = Ć
,這樣的字元範圍就會被轉換為 [Ā-Ć]
從而導致錯誤。
這時就需要一種“對小寫字元無效”的轉換方式了,將大寫字元按照奇偶分為兩類:
- 如果大寫字元為偶數(例如上面的
Ā (\u0100)
),就選擇LowercaseBor
操作,$lower = upper | 1$,這時大寫字元(偶數)或 1 後會變成小寫字元(奇數);而小寫字元或 1 後仍然是原值。 - 如果大寫字元為奇數(例如
Ĺ (\u0139)
),就選擇LowercaseBad
操作,$lower = upper + (upper & 1)$,這時大寫字元(奇數)加上其最低位後會變成小寫字元(偶數);而小寫字元最低為是 0,加上之後仍然是原值。
RegexCharClass
只包含了將大寫字元轉換為小寫字元的能力,需要自己擴展從小寫字元轉換為大寫字元的操作。
第一類操作也是類似的,同樣利用 Add
和 Set
就可以實現小寫字元到大寫字元的轉換。第二類操作也同樣需要區分奇偶,只是操作方式不太相同:
- 如果小寫字元為奇數(例如上面的
ā (\u0101)
),就使用 $upper = lower & 0xFFFE$,這時小寫字元(奇數)的最後一位會被置 0,變成大寫字元(偶數);而大寫字元最後一位置 0 後仍然是原值。 - 如果小寫字元為偶數(例如
ŀ (\u0140)
),就使用 $upper = lower - (~lower & 1)$,這時小寫字元(偶數)最低為取反後是 1,減去就會會變成大寫字元(奇數);而大寫字元最低為是 1,取反後仍然是 0,減去後會保持原值不變。
操作已經有了,就要計算所有小寫字元的範圍。這時我發現 RegexCharClass
內置的字元範圍只包含了部分 InvariantCulture
中的小寫字元,不太好確認正則表達式庫是受限於歷史問題還是使用了舊版 Unicode 標準。
同時,不同區域的大小寫範圍並不完全一致,要實現相容其它區域性的範圍大小寫轉換,就需要在運行時產出映射表,而非依靠提前計算。
我在這裡實現了一個運行時計算方案,實測下來在 InvariantCulture 區域性下得到了 169 個大寫字元範圍和 189 個小寫字元範圍,在 zh-CN 區域性下得到了 170 個大寫字元範圍和 190 個小寫字元範圍。其中的差異就是在 zh-CN 區域性下,İ
會對應的小寫字元 i
。
完整的字元範圍大小寫轉換可以在這裡找到。
作者:CYJB
出處:http://www.cnblogs.com/cyjb/
GitHub:https://github.com/CYJB/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。