正确地反转一个字符串

大多数情况下,当人们不得不反转一个字符串时,他们会或多或少地这样做:

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);
    }

并且 - 哦,快乐 - 你会意识到如果你这样做正确,它也适用于亚洲/南亚/东亚语言(以及法语/瑞典语/挪威语等)……