正確地反轉一個字串
大多數情況下,當人們不得不反轉一個字串時,他們會或多或少地這樣做:
char[] a = s.ToCharArray();
System.Array.Reverse(a);
string r = new string(a);
然而,這些人沒有意識到的是,這實際上是錯誤的。
我並不是因為缺少 NULL 檢查。
它實際上是錯誤的,因為 Glyph / GraphemeCluster 可以由幾個程式碼點(也就是字元)組成。
要知道為什麼會這樣,我們首先必須意識到角色一詞的實際含義。
角色是一個超載的術語,可能意味著許多事情。
程式碼點是資訊的原子單位。文字是一系列程式碼點。每個程式碼點都是一個由 Unicode 標準賦予的數字。
字素是一個或多個程式碼點的序列,它們顯示為單個圖形單元,讀者將其識別為書寫系統的單個元素。例如,a 和ä都是字形,但它們可能由多個程式碼點組成(例如,ä可能是兩個程式碼點,一個用於基本字元 a,後面跟一個用於 diaresis;但也有一個替代的,遺留的,單個程式碼代表這個字形的點)。某些程式碼點永遠不會是任何字形的一部分(例如,零寬度非聯結器或方向覆蓋)。
字形是一種影象,通常以字型(字形集合)儲存,用於表示字形或其部分。字型可以將多個字形組合成單個表示,例如,如果上面的ä是單個程式碼點,則字型可以選擇將其呈現為兩個單獨的,空間上重疊的字形。對於 OTF,字型的 GSUB 和 GPOS 表包含替換和定位資訊以使其工作。字型也可以包含同一字素的多個替代字形。
所以在 C#中,一個字元實際上是一個 CodePoint。
這意味著,如果你只是反轉像 Les Misérables
這樣的有效字串,它可能看起來像這樣
string s = "Les Mise\u0301rables";
作為一系列人物,你會得到:
selbaŕesiMseL
如你所見,重音在 R 字元上,而不是 e 字元。
雖然如果你們兩次反轉 char 陣列,string.reverse.reverse 將產生原始字串,但這種反轉絕對不會與原始字串相反。
你只需要反轉每個 GraphemeCluster。
所以,如果正確完成,你可以像這樣反轉一個字串:
private static System.Collections.Generic.List<string> GraphemeClusters(string s)
{
System.Collections.Generic.List<string> ls = new System.Collections.Generic.List<string>();
System.Globalization.TextElementEnumerator enumerator = System.Globalization.StringInfo.GetTextElementEnumerator(s);
while (enumerator.MoveNext())
{
ls.Add((string)enumerator.Current);
}
return ls;
}
// this
private static string ReverseGraphemeClusters(string s)
{
if(string.IsNullOrEmpty(s) || s.Length == 1)
return s;
System.Collections.Generic.List<string> ls = GraphemeClusters(s);
ls.Reverse();
return string.Join("", ls.ToArray());
}
public static void TestMe()
{
string s = "Les Mise\u0301rables";
// s = "noël";
string r = ReverseGraphemeClusters(s);
// This would be wrong:
// char[] a = s.ToCharArray();
// System.Array.Reverse(a);
// string r = new string(a);
System.Console.WriteLine(r);
}
並且 - 哦,快樂 - 你會意識到如果你這樣做正確,它也適用於亞洲/南亞/東亞語言(以及法語/瑞典語/挪威語等)……