如何反转包含代理项对的字符串

时间:2014-03-01 13:00:05

标签: c# string reverse utf-16 surrogate-pairs

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

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 个答案:

答案 0 :(得分:5)

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

您似乎在Unicode Character 'LINEAR B SYLLABLE B008 A' (U+10000)之后使用十六进制代码点10000.来自Unicode character escape sequences on MSDN

  

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

     

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

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

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

答案 1 :(得分:1)

Necromancing。
这是因为您使用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

以下是如何正确反转字符串(字符串,而不是字符序列):

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)

Reference:

  

字符是一个重载的术语,可能意味着许多事情。

     

代码点是信息的原子单位。文字是一系列的   代码点。每个代码点都是一个数字,由...给出   Unicode标准。

     

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

     

字形是一种图像,通常以字体(即集合)存储   字形),用于表示字素或其部分。字体可以   将多个字形组合成单个表示,例如,if   上面的ä是单个代码点,字体可以选择将其渲染为   两个独立的,空间重叠的字形。对于OTF,字体的GSUB和   GPOS表包含要进行的替换和定位信息   这项工作。字体可能包含多个替代字形   字形也是。

答案 2 :(得分: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;
}

答案 3 :(得分: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();
        }
    }
}