4

我写了这个方法来反转一个字符串

public string Reverse(string s)
        {
            if(string.IsNullOrEmpty(s)) 
                return s;

            TextElementEnumerator enumerator =
               StringInfo.GetTextElementEnumerator(s);

            var elements = new List<char>();
            while (enumerator.MoveNext())
            {
                var cs = enumerator.GetTextElement().ToCharArray();
                if (cs.Length > 1)
                {
                    elements.AddRange(cs.Reverse());
                }
                else
                {
                    elements.AddRange(cs);
                }
            }

            elements.Reverse();
            return string.Concat(elements);
        }

现在,我不想开始讨论如何使这段代码更有效率,或者我如何可以使用一个衬垫。我知道您可以执行 Xors 和各种其他事情来潜在地改进此代码。如果我想稍后重构代码,我可以很容易地做到这一点,因为我有单元测试。

目前,这可以正确反转 BML 字符串(包括带有重音符号的字符串"Les Misérables")和包含组合字符(例如"Les Mise\u0301rables".

如果它们像这样表达,我的包含代理对的测试可以工作

Assert.AreEqual("", _stringOperations.Reverse(""));

但是如果我表达这样的代理对

Assert.AreEqual("\u10000", _stringOperations.Reverse("\u10000"));

然后测试失败。是否有一个密封的实现也支持代理对?

如果我在上面犯了任何错误,请指出这一点,因为我不是 Unicode 专家。

4

4 回答 4

5

\u10000是一个由两个字符组成的字符串:က(Unicode 代码点 1000)后跟一个0(可以通过检查s方法中的值来检测)。如果您反转两个字符,它们将不再与输入匹配。

您似乎在使用十六进制代码点 10000的 Unicode 字符“LINEAR B SYLLABLE B008 A”(U+10000) 。来自MSDN 上的 Unicode 字符转义序列

\u 十六进制数字 十六进制数字 十六进制数字 十六进制数字

\U 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字

所以你必须使用四位或八位数字。

使用\U00010000(注意大写的 U)或\uD800\uDC00代替\u10000.

于 2014-03-01T14:20:48.913 回答
1

死灵术。
发生这种情况是因为您使用List<char>.Reverse而不是List<string>.Reverse

// using System.Globalization;

TextElementEnumerator enumerator =
    StringInfo.GetTextElementEnumerator("Les Mise\u0301rables");

List<string> elements = new List<string>();
while (enumerator.MoveNext())
    elements.Add(enumerator.GetTextElement());

elements.Reverse();
string reversed = string.Concat(elements);  // selbarésiM seL

有关更多信息,请参阅 Jon Skeet 的小马视频: https ://vimeo.com/7403673

以下是正确反转字符串(字符串,而不是chars 序列)的方法:

public static class Test
{

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

请注意,您需要知道
- 字符和字形
- 字节(8 位)和代码点/符文(32 位)
- 代码点和 GraphemeCluster [32+ 位](又名 Grapheme/Glyph)之间的区别

参考:

字符是一个超载的术语,它可以表示很多东西。

代码点是信息的原子单位。文本是一系列代码点。每个代码点都是一个由 Unicode 标准赋予含义的数字。

字素是一个或多个代码点的序列,它们显示为单个图形单元,读者将其识别为书写系统的单个元素。例如,a 和 ä 都是字素,但它们可能由多个代码点组成(例如 ä 可能是两个代码点,一个用于基本字符 a,然后一个用于分叉;但也有另一种遗留的单一代码表示这个字素的点)。某些代码点绝不是任何字形的一部分(例如,零宽度非连接符或方向覆盖)。

字形是图像,通常存储在字体(字形的集合)中,用于表示字素或其部分。字体可以将多个字形组合成单个表示,例如,如果上面的 ä 是单个代码点,则字体可以选择将其呈现为两个独立的、空间重叠的字形。对于 OTF,字体的 GSUB 和 GPOS 表包含替换和定位信息以使其工作。一个字体也可能包含同一个字素的多个替代字形。

于 2016-03-30T13:55:54.740 回答
0

这是一个开始。它可能不是最快的,但它似乎确实适用于我们提出的问题。

internal static string ReverseItWithSurrogate(string stringToReverse)
{
    string result = string.Empty;

    // We want to get the string into a character array first
    char[] stringArray = stringToReverse.ToCharArray();

    // This is the object that will hold our reversed string.
    var sb = new StringBuilder();
    bool haveSurrogate = false;

    // We are starting at the back and looking at each character.  if it is a
    // low surrogate and the one prior is a high and not < 0, then we have a surrogate pair.
    for (int loopVariable = stringArray.Length - 1; loopVariable >= 0; loopVariable--)
    {
    // we cant' check the high surrogate if the low surrogate is index 0
    if (loopVariable > 0)
    {
        haveSurrogate = false;

        if (char.IsLowSurrogate(stringArray[loopVariable]) &&    char.IsHighSurrogate(stringArray[loopVariable - 1]))
       {
          sb.Append(stringArray[loopVariable - 1]);
          sb.Append(stringArray[loopVariable]);

         // and force the second character to drop from our loop
         loopVariable--;
         haveSurrogate = true;
       }

      if (!haveSurrogate)
      {
         sb.Append(stringArray[loopVariable]);
        }
       }
    else
    {
     // Now we have to handle the first item in the list if it is not a high surrogate.
      if (!haveSurrogate)
      {
        sb.Append(stringArray[loopVariable]);
       }
     }
   }

result = sb.ToString();
return result;
}
于 2014-03-31T15:06:42.423 回答
0

最好不要在 Chrome 中查看!

using System.Linq;
using System.Collections.Generic;
using System;
using System.Globalization;
using System.Diagnostics;
using System.Collections;
namespace OrisNumbers
{
    public static class IEnumeratorExtensions
    {
        public static IEnumerable<T> AsIEnumerable<T>(this IEnumerator iterator)
        {
            while (iterator.MoveNext())
            {
                yield return (T)iterator.Current;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var s = "foo  bar mañana mañana" ;
            Debug.WriteLine(s);
            Debug.WriteLine(string.Join("", StringInfo.GetTextElementEnumerator(s.Normalize()).AsIEnumerable<string>().Reverse()));
            Console.Read();
        }
    }
}
于 2015-01-28T01:01:33.770 回答