文字列を逆にする最良の方法


440

私はC#2.0(つまり、LINQは利用できません)で文字列逆関数を作成する必要があり、これを思いつきました:

public string Reverse(string text)
{
    char[] cArray = text.ToCharArray();
    string reverse = String.Empty;
    for (int i = cArray.Length - 1; i > -1; i--)
    {
        reverse += cArray[i];
    }
    return reverse;
}

個人的に私はその機能に夢中ではなく、それを行うためのより良い方法があると確信しています。ある?


51
適切な国際サポートが必要な場合、驚くほどトリッキーです。例:クロアチア語/セルビア語には2文字のlj、njなどの文字があります。「ljudi」の正しい逆は「idulj」であり、「idujl」ではありません。アラビア語、タイ語などに関しては、もっと悪いことになると思います
dbkk 2009年

temp配列を初期化して結果をその中に格納し、最後にそれを文字列に変換するのではなく、文字列を連結する方が遅いのではないでしょうか。
マフィンマン2013


5
この質問は、あなたが「最高」で何を意味するかを定義することで改善できます。最速?最も読みやすいですか?さまざまなエッジケース(nullチェック、複数の言語など)で最も信頼性が高いですか?C#と.NETのバージョン間で最も保守しやすいですか?
hypehuman

回答:


608
public static string Reverse( string s )
{
    char[] charArray = s.ToCharArray();
    Array.Reverse( charArray );
    return new string( charArray );
}

16
sambo99:ユニコードについて言及する必要はありません。C#の文字は、バイトではなくユニコード文字です。Xorの方が高速かもしれませんが、読みにくくなることを除けば、Array.Reverse()が内部的に使用するものでさえあります。
Nick Johnson、

27
@Arachnid:実際には、C#の文字はUTF-16コード単位です。補助文字を表すには2つ必要です。jaggersoft.com/csharp_standard/9.4.1.htmを参照してください。
Bradley Grainger、

4
ええ、sambo99あなたは正しいと思いますが、UTF-32を使用するのはかなりまれなケースです。そして、XORは非常に狭い範囲の値に対してのみ高速です。正しい答えは、私が想定するさまざまな長さのさまざまなメソッドを実装することです。しかし、これは明確かつ簡潔であり、これは私の意見ではメリットです。
PeteT 2008

21
Unicode制御文字により、このメソッドは非ラテン文字セットでは使用できなくなります。靴下人形を使用したJon Skeetの説明を参照してください:codeblog.jonskeet.uk/2009/11/02/…(1/4下り)またはビデオ:vimeo.com/7516539
Callum Rogers

20
サロゲートや組み合わせ文字に遭遇しないことを願っています。
dalle

183

適切に文字列を逆にここで解決策"Les Mise\u0301rables"として"selbare\u0301siM seL"。これは(アクセントの位置に注意してください)selbarésiM seLではなく、のようにレンダリングする必要があります。selbaŕesiM seLコード単位(Array.Reverseなど)に基づくほとんどの実装や、コードポイント(サロゲートペアに特別な注意を払って反転)の結果も同様です。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

public static class Test
{
    private static IEnumerable<string> GraphemeClusters(this string s) {
        var enumerator = StringInfo.GetTextElementEnumerator(s);
        while(enumerator.MoveNext()) {
            yield return (string)enumerator.Current;
        }
    }
    private static string ReverseGraphemeClusters(this string s) {
        return string.Join("", s.GraphemeClusters().Reverse().ToArray());
    }

    public static void Main()
    {
        var s = "Les Mise\u0301rables";
        var r = s.ReverseGraphemeClusters();
        Console.WriteLine(r);
    }
}

(そしてここでライブ実行の例:https : //ideone.com/DqAeMJ

それは単に書記素クラスター反復のために .NET APIを使用するだけであり、これは以前から存在していましたが、表示からは少し「隠されている」ようです。


10
非常に少数の正解の1つ、および多く IMO他のどれよりもエレガントかつ将来性、
sehe

ただし、これはロケールに依存するものでは失敗します。
R.マルティーニョフェルナンデス

7
他のほとんどの回答者が、他の方法では正しくないアプローチから何ミリ秒も削ろうとしているのはおかしいです。どのように代表。
G. Stoynev 2013

2
StringInfo(s)をインスタンス化し、SubstringByTextElements(x、1)を反復処理して、StringBuilderで新しい文字列を作成する方が、実際にはかなり高速です。

2
Jon Skeetが数年前に提供したcodeblog.jonskeet.uk/2009/11/02/…LesMisérablesの例を使用したのは少し奇妙です (Jonは解決策については言及しませんでしたが、問題をリストしただけです)。あなたが解決策を思いついたことは良かった。たぶん、ジョンスキートがタイムマシンを発明し、2009年に戻って、ソリューションで使用した問題の例を投稿しました。
barlop

126

これは驚くほど難しい問題であることが判明しています。

Array.Reverseはネイティブにコーディングされており、保守と理解が非常に簡単であるため、ほとんどの場合はArray.Reverseを使用することをお勧めします。

私がテストしたすべてのケースで、StringBuilderよりもパフォーマンスが優れているようです。

public string Reverse(string text)
{
   if (text == null) return null;

   // this was posted by petebob as well 
   char[] array = text.ToCharArray();
   Array.Reverse(array);
   return new String(array);
}

Xor使用する特定の文字列長でより高速になる可能性がある2番目のアプローチがあります

    public static string ReverseXor(string s)
    {
        if (s == null) return null;
        char[] charArray = s.ToCharArray();
        int len = s.Length - 1;

        for (int i = 0; i < len; i++, len--)
        {
            charArray[i] ^= charArray[len];
            charArray[len] ^= charArray[i];
            charArray[i] ^= charArray[len];
        }

        return new string(charArray);
    }

完全なUnicode UTF16文字セットをサポートする場合は、こちらをお読みください。代わりにそこで実装を使用してください。上記のアルゴリズムの1つを使用して文字列を実行し、文字が反転した後に文字列をクリーンアップすることで、さらに最適化できます。

以下は、StringBuilder、Array.Reverse、Xorメソッドのパフォーマンス比較です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication4
{
    class Program
    {
        delegate string StringDelegate(string s);

        static void Benchmark(string description, StringDelegate d, int times, string text)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int j = 0; j < times; j++)
            {
                d(text);
            }
            sw.Stop();
            Console.WriteLine("{0} Ticks {1} : called {2} times.", sw.ElapsedTicks, description, times);
        }

        public static string ReverseXor(string s)
        {
            char[] charArray = s.ToCharArray();
            int len = s.Length - 1;

            for (int i = 0; i < len; i++, len--)
            {
                charArray[i] ^= charArray[len];
                charArray[len] ^= charArray[i];
                charArray[i] ^= charArray[len];
            }

            return new string(charArray);
        }

        public static string ReverseSB(string text)
        {
            StringBuilder builder = new StringBuilder(text.Length);
            for (int i = text.Length - 1; i >= 0; i--)
            {
                builder.Append(text[i]);
            }
            return builder.ToString();
        }

        public static string ReverseArray(string text)
        {
            char[] array = text.ToCharArray();
            Array.Reverse(array);
            return (new string(array));
        }

        public static string StringOfLength(int length)
        {
            Random random = new Random();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                sb.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))));
            }
            return sb.ToString();
        }

        static void Main(string[] args)
        {

            int[] lengths = new int[] {1,10,15,25,50,75,100,1000,100000};

            foreach (int l in lengths)
            {
                int iterations = 10000;
                string text = StringOfLength(l);
                Benchmark(String.Format("String Builder (Length: {0})", l), ReverseSB, iterations, text);
                Benchmark(String.Format("Array.Reverse (Length: {0})", l), ReverseArray, iterations, text);
                Benchmark(String.Format("Xor (Length: {0})", l), ReverseXor, iterations, text);

                Console.WriteLine();    
            }

            Console.Read();
        }
    }
}

結果は次のとおりです。

26251 Ticks String Builder (Length: 1) : called 10000 times.
33373 Ticks Array.Reverse (Length: 1) : called 10000 times.
20162 Ticks Xor (Length: 1) : called 10000 times.

51321 Ticks String Builder (Length: 10) : called 10000 times.
37105 Ticks Array.Reverse (Length: 10) : called 10000 times.
23974 Ticks Xor (Length: 10) : called 10000 times.

66570 Ticks String Builder (Length: 15) : called 10000 times.
26027 Ticks Array.Reverse (Length: 15) : called 10000 times.
24017 Ticks Xor (Length: 15) : called 10000 times.

101609 Ticks String Builder (Length: 25) : called 10000 times.
28472 Ticks Array.Reverse (Length: 25) : called 10000 times.
35355 Ticks Xor (Length: 25) : called 10000 times.

161601 Ticks String Builder (Length: 50) : called 10000 times.
35839 Ticks Array.Reverse (Length: 50) : called 10000 times.
51185 Ticks Xor (Length: 50) : called 10000 times.

230898 Ticks String Builder (Length: 75) : called 10000 times.
40628 Ticks Array.Reverse (Length: 75) : called 10000 times.
78906 Ticks Xor (Length: 75) : called 10000 times.

312017 Ticks String Builder (Length: 100) : called 10000 times.
52225 Ticks Array.Reverse (Length: 100) : called 10000 times.
110195 Ticks Xor (Length: 100) : called 10000 times.

2970691 Ticks String Builder (Length: 1000) : called 10000 times.
292094 Ticks Array.Reverse (Length: 1000) : called 10000 times.
846585 Ticks Xor (Length: 1000) : called 10000 times.

305564115 Ticks String Builder (Length: 100000) : called 10000 times.
74884495 Ticks Array.Reverse (Length: 100000) : called 10000 times.
125409674 Ticks Xor (Length: 100000) : called 10000 times.

Xorは短い文字列の方が高速であるようです。


2
それは文字列を返しません-"new String(...)"への呼び出しでこれをラップする必要があります
Greg Beech

ところで、私はArray.Reverseの実装を見ただけで、charsに対してはネイティブに行われました... StringBuilderオプションよりもはるかに高速です。
サムサフラン

グレッグ、サンボを半減させて、彼に反対票を投じるのではなく、より良い解決策にたどり着くのがどれほどいいことか。
DOK

@ dok1-言わないでください:) @ sambo99-今私は興味をそそられ、明日はコードプロファイラーを取り出して見てみる必要があります!
グレッグビーチ、

9
これらのメソッドは、Base Multilingual Plane以外の文字を含む文字列、つまり、2つのC#文字で表されるUnicode文字> = U + 10000を処理しません。そのような文字列を正しく処理する回答を投稿しました。
Bradley Grainger、

52

LINQ(.NET Framework 3.5以降)を使用できる場合は、次の1つのライナーで短いコードが得られます。にusing System.Linq;アクセスするために追加することを忘れないでくださいEnumerable.Reverse

public string ReverseString(string srtVarable)
{
    return new string(srtVarable.Reverse().ToArray());
}

ノート:

  • 最速のバージョンではありません-Martin Niederlによると、ここで最速のバージョン より5.7倍遅いです。
  • このコードは、他の多くのオプションと同様に、あらゆる種類の複数文字の組み合わせを完全に無視するため、使用を宿題の割り当てとそのような文字を含まない文字列に制限します。このような組み合わせを正しく処理する実装については、この質問の別の回答を参照してください。

それは最も支持されたバージョンよりも約5.7倍遅いので、これを使用することはお勧めしません!
Martin Niederl、2017年

2
最速のソリューションではありませんが、ワンライナーとしては役立ちます。
adrianmp '18 / 10/18

49

文字列にUnicodeデータ(厳密には非BMP文字)が含まれている場合、投稿された他のメソッドは文字列を破損します。(これに関する詳細は私のブログにあります。)

次のコードサンプルは、BMP以外の文字を含む文字列を正しく反転します。たとえば、「\ U00010380 \ U00010381」(Ugaritic Letter Alpa、Ugaritic Letter Beta)。

public static string Reverse(this string input)
{
    if (input == null)
        throw new ArgumentNullException("input");

    // allocate a buffer to hold the output
    char[] output = new char[input.Length];
    for (int outputIndex = 0, inputIndex = input.Length - 1; outputIndex < input.Length; outputIndex++, inputIndex--)
    {
        // check for surrogate pair
        if (input[inputIndex] >= 0xDC00 && input[inputIndex] <= 0xDFFF &&
            inputIndex > 0 && input[inputIndex - 1] >= 0xD800 && input[inputIndex - 1] <= 0xDBFF)
        {
            // preserve the order of the surrogate pair code units
            output[outputIndex + 1] = input[inputIndex];
            output[outputIndex] = input[inputIndex - 1];
            outputIndex++;
            inputIndex--;
        }
        else
        {
            output[outputIndex] = input[inputIndex];
        }
    }

    return new string(output);
}

29
実際、C#の文字は16ビットのUTF-16コード単位です。補助文字は、それらのうちの2つを使用してエンコードされ、これは必要である
ブラッドリーグレインジャー

14
System.Stringは、Unicode補足文字を含む文字列のHereBeDragonsプロパティを実際に公開する必要があるようです。
ロバートロスニー2008年

4
@SebastianNegraszus:正解です。このメソッドは、文字列内のコードポイントを逆にするだけです。書記素クラスタを逆にすることは、おそらく全体的に「有用」です(しかし、最初に任意の文字列を逆にする「使用」とは何ですか?).NET Frameworkの組み込みメソッドだけで実装するのは簡単ではありません。
Bradley Grainger

2
@Richard:書記素クラスターを分割するためのルールは、結合するコードポイントを単に検出するよりも少し複雑です。詳細については、UAX#29のGrapheme クラスター境界に関するドキュメントを参照してください。
Bradley Grainger、2013

1
とても良い情報です!誰もが Array.Reverseテストの不合格のテストがありますか?そして、私はサンプル文字列全体ではなく、ユニットテストを意味テストで...それは本当に私を助けるだろう(と他の人が)この問題についての異なる人を納得させる...
アンドレイRînea

25

わかりました、「自分自身を繰り返さないでください」という目的で、次の解決策を提供します。

public string Reverse(string text)
{
   return Microsoft.VisualBasic.Strings.StrReverse(text);
}

私の理解では、この実装はVB.NETでデフォルトで利用可能であり、Unicode文字を適切に処理します。


11
これはサロゲートを適切に処理するだけです。結合マークがめちゃくちゃになるideone.com/yikdqX
R.マルティーニョフェルナンデス

17

Greg Beech unsafeは、実際にそれと同じくらい速いオプションを投稿しました(これはインプレースの反転です)。しかし、彼が彼の答えで示したように、それは完全に悲惨な考えです。

そうは言ってArray.Reverseも、最速の方法である多くのコンセンサスがあることに私は驚いています。小さな文字列のメソッドよりもはるかに高速unsafeに、文字列の反転コピー(インプレースの反転シェニガンなし)を返すアプローチはまだあります。Array.Reverse

public static unsafe string Reverse(string text)
{
    int len = text.Length;

    // Why allocate a char[] array on the heap when you won't use it
    // outside of this method? Use the stack.
    char* reversed = stackalloc char[len];

    // Avoid bounds-checking performance penalties.
    fixed (char* str = text)
    {
        int i = 0;
        int j = i + len - 1;
        while (i < len)
        {
            reversed[i++] = str[j--];
        }
    }

    // Need to use this overload for the System.String constructor
    // as providing just the char* pointer could result in garbage
    // at the end of the string (no guarantee of null terminator).
    return new string(reversed, 0, len);
}

ここにいくつかのベンチマーク結果があります

Array.Reverse文字列が大きくなると、パフォーマンスの向上が低下し、メソッドに対して消えることがわかります。ただし、中小規模の文字列の場合、この方法に勝つことは困難です。


2
大きな文字列に対するStackOverflow。
Raz Megrelidze 2014年

@rezomegreldize:うん、それは起こるだろう;)
Dan Tao

15

簡単で良い答えは、拡張メソッドを使用することです:

static class ExtentionMethodCollection
{
    public static string Inverse(this string @base)
    {
        return new string(@base.Reverse().ToArray());
    }
}

そしてここに出力があります:

string Answer = "12345".Inverse(); // = "54321"

Reverse()そしてToArray()、あなたのコードサンプルで間違った順序です。
クリスウォルシュ

@の目的は何ですか?
user5389726598465 2018

2
@ user5389726598465このリンクを参照してください:docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 'base'はC#のキーワードであるため、C#コンパイラがそれを識別子。
Dyndrilliac

14

本当に危険なゲームをプレイしたい場合、これは断然最速の方法です(Array.Reverseメソッドの約4倍の速さ)。ポインターを使用したインプレースリバースです。

私はこれを絶対にどのような用途にも推奨しないことに注意してください(このメソッドを使用してはならない理由については、こちらをご覧ください)。ただし、それが実行できること、および文字列が実際には不変ではないことは興味深いことです。安全でないコードをオンにしたら。

public static unsafe string Reverse(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return text;
    }

    fixed (char* pText = text)
    {
        char* pStart = pText;
        char* pEnd = pText + text.Length - 1;
        for (int i = text.Length / 2; i >= 0; i--)
        {
            char temp = *pStart;
            *pStart++ = *pEnd;
            *pEnd-- = temp;
        }

        return text;
    }
}

私はこれがutf16文字列に対して誤った結果を返すことを確信しています、それは本当に問題を投げかけています:)
Sam Saffron

こんにちは、このstackoverflow.com/questions/229346/…のこの投稿にリンクする必要があります。前に言ったように、これは本当に問題を求めています...
Sam Saffron

これは完全に悪と(あなた自身が認めるよう)無分別かもしれませんが、文字列使用して逆にする高性能な方法はまだありますunsafeコードではありません悪とまだビートArray.Reverse多くの場合は。私の答えを見てください。
Dan Tao

13

こちらのウィキペディアエントリをご覧ください。それらはString.Reverse拡張メソッドを実装します。これにより、次のようなコードを記述できます。

string s = "olleh";
s.Reverse();

また、この質問に対する他の回答が示唆するToCharArray / Reverseの組み合わせも使用しています。ソースコードは次のようになります。

public static string Reverse(this string input)
{
    char[] chars = input.ToCharArray();
    Array.Reverse(chars);
    return new String(chars);
}

拡張メソッドがc#2.0で導入されなかったことを除いて、それは素晴らしいことです。
コビ

11

最初にToCharArray、文字列はすでにchar配列としてインデックス付けされている可能性があるため、呼び出す必要がないため、割り当てを節約できます。

次の最適化は、StringBuilder不要な割り当てを防ぐためにaを使用することです(文字列は不変なので、それらを連結すると、毎回文字列のコピーが作成されます)。これをさらに最適化StringBuilderするために、バッファの長さを事前に設定して、バッファを拡張する必要がないようにします。

public string Reverse(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return text;
    }

    StringBuilder builder = new StringBuilder(text.Length);
    for (int i = text.Length - 1; i >= 0; i--)
    {
        builder.Append(text[i]);
    }

    return builder.ToString();
}

編集:パフォーマンスデータ

この関数と、Array.Reverse次の簡単なプログラムを使用して関数をテストしました。ここで、Reverse1は1つの関数でReverse2あり、もう1つの関数です。

static void Main(string[] args)
{
    var text = "abcdefghijklmnopqrstuvwxyz";

    // pre-jit
    text = Reverse1(text); 
    text = Reverse2(text);

    // test
    var timer1 = Stopwatch.StartNew();
    for (var i = 0; i < 10000000; i++)
    {
        text = Reverse1(text);
    }

    timer1.Stop();
    Console.WriteLine("First: {0}", timer1.ElapsedMilliseconds);

    var timer2 = Stopwatch.StartNew();
    for (var i = 0; i < 10000000; i++)
    {
        text = Reverse2(text);
    }

    timer2.Stop();
    Console.WriteLine("Second: {0}", timer2.ElapsedMilliseconds);

    Console.ReadLine();
}

短い文字列の場合、Array.Reverseメソッドは上記のものの約2倍速く、長い文字列の場合、違いはさらに顕著であることがわかります。そのため、このArray.Reverse方法はよりシンプルで高速であることを考えると、これよりもこの方法を使用することをお勧めします。これをここに置いておくのは、それがあなたがそれを行うべき方法ではないことを示すためです(私が驚いたことに!)


text.Lengthを変数に格納すると、オブジェクトを介してこれを参照しているため、速度が少し向上しませんか?
デビッドロビンス

10

Array.Reverseを使用してみてください


public string Reverse(string str)
{
    char[] array = str.ToCharArray();
    Array.Reverse(array);
    return new string(array);
}

これは信じられないほど高速です。
Michael Stum

なぜ反対票か。議論はしていませんが、私は自分の過ちから学びたいと思っています。
マイク二つの

他の多くのものの間でコードポイントの組み合わせを処理できません。
Mooing Duck、2013

@MooingDuck-説明していただきありがとうございます。コードポイントの意味がわかりません。また、「他の多くのもの」について詳しく説明してもらえますか?
マイクトゥー

@MooingDuckコードポイントを調べました。はい。あなたは正しいです。コードポイントは処理しません。このような単純な質問のすべての要件を決定することは困難です。フィードバック
マイクトゥー

10
public static string Reverse(string input)
{
    return string.Concat(Enumerable.Reverse(input));
}

もちろん、Reverseメソッドで文字列クラスを拡張できます

public static class StringExtensions
{
    public static string Reverse(this string input)
    {
        return string.Concat(Enumerable.Reverse(input));
    }
}

Enumerable.Reverse(input)と等しいinput.Reverse()
fubo

8

「最良」は多くの事柄に依存する可能性がありますが、高速から低速に並べられたいくつかの短い代替案を次に示します。

string s = "z̽a̎l͘g̈o̓😀😆", pattern = @"(?s).(?<=(?:.(?=.*$(?<=((\P{M}\p{C}?\p{M}*)\1?))))*)";

string s1 = string.Concat(s.Reverse());                          // "☐😀☐̓ög͘l̎a̽z"  👎

string s2 = Microsoft.VisualBasic.Strings.StrReverse(s);         // "😆😀o̓g̈l͘a̎̽z"  👌

string s3 = string.Concat(StringInfo.ParseCombiningCharacters(s).Reverse()
    .Select(i => StringInfo.GetNextTextElement(s, i)));          // "😆😀o̓g̈l͘a̎z̽"  👍

string s4 = Regex.Replace(s, pattern, "$2").Remove(s.Length);    // "😆😀o̓g̈l͘a̎z̽"  👍

8

.NET Core 2.1以降、このstring.Createメソッドを使用して文字列を逆にする新しい方法があります。

「Les Mise \ u0301rables」は「selbarésiMseL」に変換されるため、このソリューションはUnicode結合文字などを正しく処理しないことに注意してください。他の回答よりよい解決策のために。

public static string Reverse(string input)
{
    return string.Create<string>(input.Length, input, (chars, state) =>
    {
        state.AsSpan().CopyTo(chars);
        chars.Reverse();
    });
}

これは基本的にの文字をinput新しい文字列にコピーし、新しい文字列を元の場所に戻します。

なぜstring.Create役立つのですか?

既存の配列から文字列を作成すると、新しい内部配列が割り当てられ、値がコピーされます。それ以外の場合は、(安全な環境で)作成後に文字列を変更することができます。つまり、次のスニペットでは、長さ10の配列を2回割り当てる必要があります。1つはバッファとして、もう1つは文字列の内部配列として割り当てます。

var chars = new char[10];
// set array values
var str = new string(chars);

string.Create基本的に、文字列の作成時に内部配列を操作できます。つまり、バッファはもう必要ないため、その1つのchar配列を割り当てることを回避できます。

スティーブゴードンはそれについてここでより詳細に書いていますMSDNにも記事があります。

使い方はstring.Create

public static string Create<TState>(int length, TState state, SpanAction<char, TState> action);

このメソッドは次の3つのパラメーターを取ります。

  1. 作成する文字列の長さ、
  2. 新しい文字列を動的に作成するために使用するデータ
  3. そして、データから最終的な文字列を作成するデリゲート。最初のパラメーターcharは新しい文字列の内部配列を指し、2番目のパラメーターはに渡したデータ(状態)ですstring.Create

デリゲート内では、データから新しい文字列を作成する方法を指定できます。この例では、入力文字列の文字をSpan新しい文字列で使用される文字にコピーするだけです。次に、を逆にSpanします。したがって、文字列全体が逆になります。

ベンチマーク

文字列を逆にする私の提案された方法と受け入れられた答えを比較するために、BenchmarkDotNetを使用して2つのベンチマークを記述しました。

public class StringExtensions
{
    public static string ReverseWithArray(string input)
    {
        var charArray = input.ToCharArray();
        Array.Reverse(charArray);
        return new string(charArray);
    }

    public static string ReverseWithStringCreate(string input)
    {
        return string.Create(input.Length, input, (chars, state) =>
        {
            state.AsSpan().CopyTo(chars);
            chars.Reverse();
        });
    }
}

[MemoryDiagnoser]
public class StringReverseBenchmarks
{
    private string input;

    [Params(10, 100, 1000)]
    public int InputLength { get; set; }


    [GlobalSetup]
    public void SetInput()
    {
        // Creates a random string of the given length
        this.input = RandomStringGenerator.GetString(InputLength);
    }

    [Benchmark(Baseline = true)]
    public string WithReverseArray() => StringExtensions.ReverseWithArray(input);

    [Benchmark]
    public string WithStringCreate() => StringExtensions.ReverseWithStringCreate(input);
}

これが私のマシンでの結果です:

| Method           | InputLength |         Mean |      Error |    StdDev |  Gen 0 | Allocated |
| ---------------- | ----------- | -----------: | ---------: | --------: | -----: | --------: |
| WithReverseArray | 10          |    45.464 ns |  0.4836 ns | 0.4524 ns | 0.0610 |      96 B |
| WithStringCreate | 10          |    39.749 ns |  0.3206 ns | 0.2842 ns | 0.0305 |      48 B |
|                  |             |              |            |           |        |           |
| WithReverseArray | 100         |   175.162 ns |  2.8766 ns | 2.2458 ns | 0.2897 |     456 B |
| WithStringCreate | 100         |   125.284 ns |  2.4657 ns | 2.0590 ns | 0.1473 |     232 B |
|                  |             |              |            |           |        |           |
| WithReverseArray | 1000        | 1,523.544 ns |  9.8808 ns | 8.7591 ns | 2.5768 |    4056 B |
| WithStringCreate | 1000        | 1,078.957 ns | 10.2948 ns | 9.6298 ns | 1.2894 |    2032 B |

ご覧のとおりReverseWithStringCreate、ではReverseWithArrayメソッドによって使用されるメモリの半分のみが割り当てられます。


Linqリバースよりもはるかに高速です
-code4j

7

関数を気にせず、その場で実行してください。注:2行目は、一部のVSバージョンのイミディエイトウィンドウで引数の例外をスローします。

string s = "Blah";
s = new string(s.ToCharArray().Reverse().ToArray()); 

1
一部の人は、理由を説明せずにすべての回答(私のものを含む)に反対票を投じるために時間をかけました。
Marcel Valdez Orozco、2013年

あなたはnew string
mbadawi23

5

長い投稿で申し訳ありませんが、これは興味深いかもしれません

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        public static string ReverseUsingArrayClass(string text)
        {
            char[] chars = text.ToCharArray();
            Array.Reverse(chars);
            return new string(chars);
        }

        public static string ReverseUsingCharacterBuffer(string text)
        {
            char[] charArray = new char[text.Length];
            int inputStrLength = text.Length - 1;
            for (int idx = 0; idx <= inputStrLength; idx++) 
            {
                charArray[idx] = text[inputStrLength - idx];                
            }
            return new string(charArray);
        }

        public static string ReverseUsingStringBuilder(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return text;
            }

            StringBuilder builder = new StringBuilder(text.Length);
            for (int i = text.Length - 1; i >= 0; i--)
            {
                builder.Append(text[i]);
            }

            return builder.ToString();
        }

        private static string ReverseUsingStack(string input)
        {
            Stack<char> resultStack = new Stack<char>();
            foreach (char c in input)
            {
                resultStack.Push(c);
            }

            StringBuilder sb = new StringBuilder();
            while (resultStack.Count > 0)
            {
                sb.Append(resultStack.Pop());
            }
            return sb.ToString();
        }

        public static string ReverseUsingXOR(string text)
        {
            char[] charArray = text.ToCharArray();
            int length = text.Length - 1;
            for (int i = 0; i < length; i++, length--)
            {
                charArray[i] ^= charArray[length];
                charArray[length] ^= charArray[i];
                charArray[i] ^= charArray[length];
            }

            return new string(charArray);
        }


        static void Main(string[] args)
        {
            string testString = string.Join(";", new string[] {
                new string('a', 100), 
                new string('b', 101), 
                new string('c', 102), 
                new string('d', 103),                                                                   
            });
            int cycleCount = 100000;

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingCharacterBuffer(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingCharacterBuffer: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingArrayClass(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingArrayClass: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingStringBuilder(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingStringBuilder: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingStack(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingStack: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingXOR(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingXOR: " + stopwatch.ElapsedMilliseconds + "ms");            
        }
    }
}

結果:

  • ReverseUsingCharacterBuffer:346ms
  • ReverseUsingArrayClass:87ms
  • ReverseUsingStringBuilder:824ms
  • ReverseUsingStack:2086ms
  • ReverseUsingXOR:319ms

投稿に同様の比較を追加しました。コミュニティwikiなので、編集できるはずです。パフォーマンスは実際には文字列の長さとアルゴリズムに依存します。それをグラフ化すると興味深いでしょう。私は今でもArray.Reverseがすべての場合で最速になると思います...
Sam Saffron

魔法のTrySZReverse関数(Reverse実装で使用されます)が失敗すると、Array.Reverseはボクシングを含む単純な実装にフォールバックします。ただし、TrySZReverseが失敗する条件は何なのかわかりません。
08/10/23

すべてのケースで最速ではないことが判明しました:)、私の投稿を更新しました。これは、正確さと速度の両方について、まだユニコードでテストする必要があります。
サムサフラン

5
public string Reverse(string input)
{
    char[] output = new char[input.Length];

    int forwards = 0;
    int backwards = input.Length - 1;

    do
    {
        output[forwards] = input[backwards];
        output[backwards] = input[forwards];
    }while(++forwards <= --backwards);

    return new String(output);
}

public string DotNetReverse(string input)
{
    char[] toReverse = input.ToCharArray();
    Array.Reverse(toReverse);
    return new String(toReverse);
}

public string NaiveReverse(string input)
{
    char[] outputArray = new char[input.Length];
    for (int i = 0; i < input.Length; i++)
    {
        outputArray[i] = input[input.Length - 1 - i];
    }

    return new String(outputArray);
}    

public string RecursiveReverse(string input)
{
    return RecursiveReverseHelper(input, 0, input.Length - 1);
}

public string RecursiveReverseHelper(string input, int startIndex , int endIndex)
{
    if (startIndex == endIndex)
    {
        return "" + input[startIndex];
    }

    if (endIndex - startIndex == 1)
    {
        return "" + input[endIndex] + input[startIndex];
    }

    return input[endIndex] + RecursiveReverseHelper(input, startIndex + 1, endIndex - 1) + input[startIndex];
}


void Main()
{
    int[] sizes = new int[] { 10, 100, 1000, 10000 };
    for(int sizeIndex = 0; sizeIndex < sizes.Length; sizeIndex++)
    {
        string holaMundo  = "";
        for(int i = 0; i < sizes[sizeIndex]; i+= 5)
        {   
            holaMundo += "ABCDE";
        }

        string.Format("\n**** For size: {0} ****\n", sizes[sizeIndex]).Dump();

        string odnuMaloh = DotNetReverse(holaMundo);

        var stopWatch = Stopwatch.StartNew();
        string result = NaiveReverse(holaMundo);
        ("Naive Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = Reverse(holaMundo);
        ("Efficient linear Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = RecursiveReverse(holaMundo);
        ("Recursive Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = DotNetReverse(holaMundo);
        ("DotNet Reverse Ticks: " + stopWatch.ElapsedTicks).Dump();
    }
}

出力

サイズ:10

Naive Ticks: 1
Efficient linear Ticks: 0
Recursive Ticks: 2
DotNet Reverse Ticks: 1

サイズ:100

Naive Ticks: 2
Efficient linear Ticks: 1
Recursive Ticks: 12
DotNet Reverse Ticks: 1

サイズ:1000

Naive Ticks: 5
Efficient linear Ticks: 2
Recursive Ticks: 358
DotNet Reverse Ticks: 9

サイズ:10000

Naive Ticks: 32
Efficient linear Ticks: 28
Recursive Ticks: 84808
DotNet Reverse Ticks: 33

1
で空の文字列を確認する必要がありますReverse(...)。そうでなければ、良い仕事。
ララ


4

スタックベースのソリューション。

    public static string Reverse(string text)
    {
        var stack = new Stack<char>(text);
        var array = new char[stack.Count];

        int i = 0;
        while (stack.Count != 0)
        {
            array[i++] = stack.Pop();
        }

        return new string(array);
    }

または

    public static string Reverse(string text)
    {
        var stack = new Stack<char>(text);
        return string.Join("", stack);
    }

4

再帰的な例を提出しなければなりませんでした:

private static string Reverse(string str)
{
    if (str.IsNullOrEmpty(str) || str.Length == 1)
        return str;
    else
        return str[str.Length - 1] + Reverse(str.Substring(0, str.Length - 1));
}

1
長さ0の文字列は処理されません
bohdan_trotsenko 2013年

これは役に立ちません。
user3613932

3

どうですか:

    private string Reverse(string stringToReverse)
    {
        char[] rev = stringToReverse.Reverse().ToArray();
        return new string(rev); 
    }

上記の他の方法と同じコードポイントの問題があり、ToCharArray最初の方法よりもはるかに遅く実行されます。LINQ列挙子は、に比べてかなり低速ですArray.Reverse()
アベル

3

Microsoft.VisualBasic.StringsからC#ポートを作成しました。彼らがなぜフレームワークのSystem.Stringの外で(VBから)そのような便利な関数を保持しているのかはわかりませんが、それでもMicrosoft.VisualBasicの下にあります。財務機能と同じシナリオ(例:Microsoft.VisualBasic.Financial.Pmt()

public static string StrReverse(this string expression)
{
    if ((expression == null))
        return "";

    int srcIndex;

    var length = expression.Length;
    if (length == 0)
        return "";

    //CONSIDER: Get System.String to add a surrogate aware Reverse method

    //Detect if there are any graphemes that need special handling
    for (srcIndex = 0; srcIndex <= length - 1; srcIndex++)
    {
        var ch = expression[srcIndex];
        var uc = char.GetUnicodeCategory(ch);
        if (uc == UnicodeCategory.Surrogate || uc == UnicodeCategory.NonSpacingMark || uc == UnicodeCategory.SpacingCombiningMark || uc == UnicodeCategory.EnclosingMark)
        {
            //Need to use special handling
            return InternalStrReverse(expression, srcIndex, length);
        }
    }

    var chars = expression.ToCharArray();
    Array.Reverse(chars);
    return new string(chars);
}

///<remarks>This routine handles reversing Strings containing graphemes
/// GRAPHEME: a text element that is displayed as a single character</remarks>
private static string InternalStrReverse(string expression, int srcIndex, int length)
{
    //This code can only be hit one time
    var sb = new StringBuilder(length) { Length = length };

    var textEnum = StringInfo.GetTextElementEnumerator(expression, srcIndex);

    //Init enumerator position
    if (!textEnum.MoveNext())
    {
        return "";
    }

    var lastSrcIndex = 0;
    var destIndex = length - 1;

    //Copy up the first surrogate found
    while (lastSrcIndex < srcIndex)
    {
        sb[destIndex] = expression[lastSrcIndex];
        destIndex -= 1;
        lastSrcIndex += 1;
    }

    //Now iterate through the text elements and copy them to the reversed string
    var nextSrcIndex = textEnum.ElementIndex;

    while (destIndex >= 0)
    {
        srcIndex = nextSrcIndex;

        //Move to next element
        nextSrcIndex = (textEnum.MoveNext()) ? textEnum.ElementIndex : length;
        lastSrcIndex = nextSrcIndex - 1;

        while (lastSrcIndex >= srcIndex)
        {
            sb[destIndex] = expression[lastSrcIndex];
            destIndex -= 1;
            lastSrcIndex -= 1;
        }
    }

    return sb.ToString();
}

+1、素晴らしい追加です!私はで試したところ、BMPの3つの結合クリティカル記号と、アストラルプレーン(非BMP)の1つの結合マーク(MUSICAL SYMBOL COMBINING STEM)が付いstring s = "abo\u0327\u0307\u035d\U0001d166cd"た文字oが含まれており、そのまま保持されます。ただし、配列全体を2回検索する必要があるため、このような文字が長い文字列の最後にしか表示されない場合、メソッドは遅くなります。
アベル

3

この古いスレッドに投稿して申し訳ありません。インタビューのコードを練習しています。

これは私がC#のために思いついたものです。リファクタリング前の最初のバージョンは恐ろしいものでした。

static String Reverse2(string str)
{
    int strLen = str.Length, elem = strLen - 1;
    char[] charA = new char[strLen];

    for (int i = 0; i < strLen; i++)
    {
        charA[elem] = str[i];
        elem--;
    }

    return new String(charA);
}

Array.Reverse以下の方法とは対照的に、文字列の文字数が12文字以下の方が高速に表示されます。13文字を超えると、Array.Reverseはより速くなり始め、最終的には速度に大きく影響します。速度が変わり始めるおおよその点を指摘したかっただけです。

static String Reverse(string str)
{     
    char[] charA = str.ToCharArray();

    Array.Reverse(charA);

    return new String(charA);
}

文字列が100文字である場合、バージョン4よりも高速です。ただし、文字列が常に13文字未満であることがわかっている場合は、作成した文字列を使用します。

テストはStopwatch5000000回の反復で行われました。また、私のバージョンがサロゲートを処理するのか、Unicodeエンコードと組み合わせた文字の状況を処理するのかわかりません。


2

「より良い方法」は、状況、パフォーマンス、優雅さ、保守性などにおいて、あなたにとって何がより重要かによって異なります。

とにかく、ここにArray.Reverseを使用したアプローチがあります。

string inputString="The quick brown fox jumps over the lazy dog.";
char[] charArray = inputString.ToCharArray(); 
Array.Reverse(charArray); 

string reversed = new string(charArray);

2

インタビューでそれが出てきて、Array.Reverseを使用できないと言われた場合は、これが最も速い方法の1つだと思います。新しい文字列を作成せず、配列の半分だけを反復します(つまり、O(n / 2)の反復)

    public static string ReverseString(string stringToReverse)
    {
        char[] charArray = stringToReverse.ToCharArray();
        int len = charArray.Length-1;
        int mid = len / 2;

        for (int i = 0; i < mid; i++)
        {
            char tmp = charArray[i];
            charArray[i] = charArray[len - i];
            charArray[len - i] = tmp;
        }
        return new string(charArray);
    }

2
stringToReverse.ToCharArray()呼び出しがO(N)実行時間を生成すると確信しています。
Marcel Valdez Orozco 2012

ビッグO記法、に依存しない要因x、またはあなたのケースでは、n使用されていません。アルゴリズムにはパフォーマンスf(x) = x + ½x + Cがありますが、Cは定数です。Cとの両方がに依存していないxため、アルゴリズムはO(x)です。これは、長さのどの入力に対しても高速にならないという意味ではありませんxが、そのパフォーマンスは入力の長さに線形に依存します。@MarcelValdezOrozcoに答えるには、はい、それもですO(n)。ただし、速度を向上させるために16バイトのチャンクごとにコピーします(memcpy全長にストレートを使用していません)。
アベル

2

ASCII文字のみを含む文字列がある場合は、この方法を使用できます。

    public static string ASCIIReverse(string s)
    {
        byte[] reversed = new byte[s.Length];

        int k = 0;
        for (int i = s.Length - 1; i >= 0; i--)
        {
            reversed[k++] = (byte)s[i];
        }

        return Encoding.ASCII.GetString(reversed);
    }

2

まず理解しておく必要があるのは、str + =が文字列メモリのサイズを変更して、1文字余分にスペースを空けることです。これは問題ありませんが、たとえば、反転する1000ページのブックがある場合、実行に非常に時間がかかります。

一部の人が提案するかもしれない解決策は、StringBuilderを使用することです。+ =を実行するときに文字列ビルダーが行うことは、文字を追加するたびに再割り当てを行う必要がないように、新しい文字を保持するためにはるかに大きなメモリチャンクを割り当てることです。

あなたが本当に速くて最小限のソリューションを望んでいるなら、私は以下を提案するでしょう:

            char[] chars = new char[str.Length];
            for (int i = str.Length - 1, j = 0; i >= 0; --i, ++j)
            {
                chars[j] = str[i];
            }
            str = new String(chars);

このソリューションでは、char []が初期化されるときに1つの初期メモリ割り当てがあり、文字列コンストラクターがchar配列から文字列を構築するときに1つの割り当てがあります。

私のシステムでは、2 750 000文字の文字列を反転させるテストを実行しました。以下は、10回の実行の結果です。

StringBuilder:190K-200Kティック

Char配列:130K-160Kティック

また、通常の文字列+ =のテストも実行しましたが、10分後に何も出力せずに中止しました。

ただし、文字列が小さい場合はStringBuilderの方が高速であるため、入力に基づいて実装を決定する必要があることにも気付きました。

乾杯


動作しない😀Les Misérables
チャールズ

@チャールズああそうだと思う文字セットの制限があります。
Reasurria

2
public static string reverse(string s) 
{
    string r = "";
    for (int i = s.Length; i > 0; i--) r += s[i - 1];
    return r;
}

1
public static string Reverse2(string x)
        {
            char[] charArray = new char[x.Length];
            int len = x.Length - 1;
            for (int i = 0; i <= len; i++)
                charArray[i] = x[len - i];
            return new string(charArray);
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.