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

ここで、このコードをより効率的にする方法や、代わりに使用できるライナーが 1 つある方法についての議論を開始したくありません。このコードを潜在的に改善するために、Xor やその他のあらゆる種類のことを実行できることを認識しています。後でコードをリファクタリングしたい場合は、単体テストがあるので簡単に行うことができます。

現在、これは BML 文字列 ( のようなアクセント付きの文字"Les Misérables"列を含む) と、 のような結合文字を含む文字列を正しく反転し"Les Mise\u0301rables"ます。

サロゲートペアを含む私のテストは、次のように表現されている場合に機能します

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

しかし、サロゲートペアをこのように表現すると

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

その後、テストは失敗します。サロゲートペアもサポートする気密実装はありますか?

私が上記の間違いを犯した場合、私は Unicode の専門家ではないので、これを指摘してください。

4

4 に答える 4

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 標準によって意味が与えられる数値です。

書記素は、1 つまたは複数のコード ポイントのシーケンスであり、読み手が書記体系の 1 つの要素として認識する 1 つのグラフィカルな単位として表示されます。たとえば、a と ä は両方とも書記素ですが、それらは複数のコード ポイントで構成されている場合があります (たとえば、ä は 2 つのコード ポイントで、1 つは基本文字 a で、もう 1 つは分音記号です。ただし、代替の従来の単一コードもあります)。この書記素を表す点)。一部のコード ポイントは、書記素の一部ではありません (たとえば、ゼロ幅の非結合子、または方向オーバーライド)。

グリフはイメージであり、通常はフォント (グリフのコレクション) に格納され、書記素またはその一部を表すために使用されます。フォントは、複数のグリフを単一の表現に構成する場合があります。たとえば、上記の ä が単一のコード ポイントである場合、フォントは、空間的に重ね合わされた 2 つの別個のグリフとしてレンダリングすることを選択できます。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 に答える