正确地反转一个字符串
大多数情况下,当人们不得不反转一个字符串时,他们会或多或少地这样做:
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);
}
并且 - 哦,快乐 - 你会意识到如果你这样做正确,它也适用于亚洲/南亚/东亚语言(以及法语/瑞典语/挪威语等)……