大多数情况下,当人们必须反转字符串时,他们或多或少会这样做:
char[] a = s.ToCharArray(); System.Array.Reverse(a); string r = new string(a);
然而,这些人没有意识到的是,这实际上是错误的。
我的意思不是因为缺少 NULL 检查。
它实际上是错误的,因为 Glyph/GraphemeCluster 可以由多个代码点(又名字符)组成。
要了解为什么会这样,我们首先必须了解“字符”一词的实际含义这一事实。
性格是一个超载的术语,它可以意味着很多东西。
代码点是信息的原子单位。文本是一系列代码点。每个代码点都是一个数字,由 Unicode 标准赋予其含义。
字素是一个或多个代码点的序列,显示为单个图形单元,读者将其识别为书写系统的单个元素。例如,a 和 ä 都是字素,但它们可能由多个代码点组成(例如 ä 可能是两个代码点,一个用于基本字符 a 后跟一个用于日记;但还有一种替代的遗留单一代码代表这个字素的点)。某些代码点从不属于任何字素(例如,零宽度非连接器或方向覆盖)。
字形是图像,通常存储在字体(字形的集合)中,用于表示字素或其部分。字体可以将多个字形组合成单个表示,例如,如果上述 ä 是单个代码点,则字体可能会选择将其呈现为两个独立的、空间重叠的字形。对于 OTF,字体的 GSUB 和 GPOS 表包含替换和定位信息来完成这项工作。一个字体也可能包含同一个字素的多个替代字形。
所以在 C# 中,一个字符实际上是一个 CodePoint。
这意味着,如果你只是反转一个像 那样的有效字符串Les Misérables,它看起来像这样
string s = "Les Mise\u0301rables";
作为字符序列,您将获得:
selbaŕesiM seL
如您所见,重音位于 R 字符上,而不是 e 字符上。
虽然 string.reverse.reverse 会在两次反转 char 数组时产生原始字符串,但这种反转绝对不是原始字符串的反转。
您只需要反转每个 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; } // 这 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); // 这 would be wrong: // char[] a = s.ToCharArray(); // System.Array.Reverse(a); // 字符串 r = 新字符串(a); System.Console.WriteLine(r); }
而且 - 哦,喜悦 - 你会意识到如果你这样做正确,它也适用于亚洲/南亚/东亚语言(以及法语/瑞典语/挪威语等)......