反转字符串的最佳方法


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
如果您需要适当的国际支持,则非常棘手。示例:克罗地亚语/塞尔维亚语具有两个字符的字母lj,nj等。“ ljudi”的正确反向是“ idulj”,而不是“ idujl”。我相信您在阿拉伯语,泰语等方面的
表现

我想知道,连接一个字符串而不是初始化一个临时数组并将结果存储在其中,然后最终将其转换为字符串是否更慢?
松饼人


5
通过定义“最佳”的含义可以改善这个问题。最快的?最易读?在各种情况下(空检查,多种语言等)最可靠?在C#和.NET版本之间最可维护?
hypehuman

Answers:


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

16
sambo99:不需要提及unicode:C#中的char是unicode字符,而不是字节。Xor可能更快,但是除了可读性差得多之外,它甚至可能是Array.Reverse()在内部使用的东西。
尼克·约翰逊

27
@Arachnid:实际上,C#中的字符是UTF-16代码单元;需要两个来代表一个补充字符。参见jaggersoft.com/csharp_standard/9.4.1.htm
Bradley Grainger

4
是的sambo99我想您是正确的,但使用UTF-32的情况很少。而且XOR仅对于很小的值范围才更快,正确的答案是为我想的不同长度实现不同的方法。但这很清楚,我认为这是有益的。
PeteT

21
Unicode控制字符使此方法对非拉丁字符集无效。参见乔恩·斯凯特(Jon Skeet)的解释,使用一个袜子木偶:codeblog.jonskeet.uk/2009/11/02/…(下移1/4)或视频:vimeo.com/7516539
Callum Rogers 2010年

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 之一,这是极少数正确的答案之一,它典雅性和未来证明要远远多于IMO
sehe

但是,这对于某些与语言环境有关的东西来说是失败的。
R. Martinho Fernandes

7
有趣的是,大多数其他答复者都试图从其他原本不正确的方法中抹除掉一些错误。多么有代表性。
G. Stoynev

2
实例化StringInfo,然后遍历SubstringByTextElements(x,1)并使用StringBuilder构建新字符串实际上要快得多。

2
您使用了乔恩·斯凯特(Jon Skeet)早些年给他的示例codeblog.jonskeet.uk/2009/11/02/…有点奇怪 。很好,您想出了一个解决方案。也许乔恩·斯凯特(Jon skeet)发明了一种时光机,回到了2009年,并发布了您在解决方案中使用的问题示例。
barlop

126

事实证明,这是一个令人惊讶的棘手问题。

我建议在大多数情况下使用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可以更快。

    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字符集,请阅读此内容。并改用那里的实现。通过使用上述算法之一,并在反转字符后遍历字符串以清理字符串,可以进一步对其进行优化。

这是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选项要快得多。
山姆·萨弗隆

格雷格(Greg),您真心地阻止桑博(Sambo)提供了一个更好的解决方案,而不是否决他。
DOK

@ dok1-别说了:) @ sambo99-现在我很感兴趣,明天将不得不抽出代码分析器并看看!
格雷格·比奇

9
这些方法不处理包含基本多语言平面之外的字符的字符串,即用两个C#字符表示的Unicode字符> = U + 10000。我已经发布了正确处理此类字符串的答案。
Bradley Grainger

52

如果您可以使用LINQ(.NET Framework 3.5+),则遵循下面的划线将为您提供简短的代码。不要忘记添加using System.Linq;以访问Enumerable.Reverse

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

笔记:

  • 不是最快的版本-根据 Martin Niederl的说法这里的速度比慢5.7倍。
  • 此代码以及许多其他选项完全忽略了所有多种字符组合,因此将用法限制为作业分配和包含此类字符的字符串。有关正确处理此类组合的实现,请参见此问题中的另一个答案

这比最推荐的版本慢5.7倍,所以我不建议使用此版本!
Martin Niederl '17

2
这不是最快的解决方案,但作为单行有用。
adrianmp

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#中的char是16位UTF-16代码单元;补码使用其中两个进行编码,因此这是必要的,
Bradley Grainger

14
似乎System.String确实应该为包含Unicode补充字符的字符串公开HereBeDragons属性。
罗伯特·罗斯尼

4
@SebastianNegraszus:没错:此方法只是反转字符串中的代码点。反转字素总体上可能会更“有用”(但是首先反转任意字符串的“用途”是什么?),但仅使用.NET Framework中的内置方法很难实现。
Bradley Grainger 2012年

2
@Richard:打破字素簇的规则比仅仅检测组合的代码点要复杂一些。有关更多信息,请参阅UAX#29中有关Grapheme群集边界的文档。
Bradley Grainger

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. Martinho Fernandes

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'7

2
@ user5389726598465请参阅此链接:docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 因为'base'是C#中的关键字,所以C#编译器必须在它前面加上@前缀才能将其解释为标识符。
Dyndrilliac

14

如果您想玩一款非常危险的游戏,那么这是目前为止最快的方式(大约是普通游戏的四倍) Array.Reverse方法)。这是使用指针的就地反向。

请注意,我真的不建议将此方法用于任何用途(出于某些原因,不建议使用此方法请在此处查看),但是很有趣的是它可以完成,并且字符串并不是真正不变的。一旦您打开不安全的代码。

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's

嗨,您应该链接到此stackoverflow.com/questions/229346/…上的帖子,正如我在此之前所说的那样,这确实是在自找麻烦……
Sam Saffron

这可能完全是邪恶的,并且是不明智的(正如您自己承认的那样),但是仍然存在一种高性能的方法,可以使用邪恶的unsafe代码来反转字符串,并且在许多情况下仍然可以跳动。看看我的答案。Array.Reverse
丹涛2010年

13

这里查看Wikipedia条目。它们实现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数组,因此这将节省您的分配。

下一个优化是使用a StringBuilder来防止不必要的分配(因为字符串是不可变的,将它们串联在一起每次都会得到该字符串的副本)。为了进一步优化它,我们预先设置了的长度,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一个是功能,Reverse2另一个是:

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方法的速度约为上述方法的两倍,而对于较长的字符串,此方法的区别更为明显。因此,鉴于该Array.Reverse方法既简单又快速,我建议您使用该方法,而不要使用此方法。我把这个留在这里只是为了表明这不是您应该做的方式(令我惊讶的是!)


当您通过对象引用此变量时,不会存储text.Length来提高速度吗?
大卫·罗宾斯

10

尝试使用Array.Reverse


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

这非常快。
迈克尔·斯托姆

为什么要投反对票?没有争论,但是我想从我的错误中学习。
麦克两

无法处理许多其他事项之间的组合代码点。
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方法来反转字符串的新方法。

请注意,此解决方案无法正确处理Unicode组合字符等,因为“ Les Mise \ u0301rables”将转换为“selbarésiMseL”。在其他的答案一个更好的解决方案。

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的数组两次,一个作为缓冲区,一个作为字符串的内部数组。

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

string.Create本质上允许我们在字符串创建期间操纵内部数组。也就是说,我们不再需要缓冲区,因此可以避免分配一个char数组。

史蒂夫·戈登(Steve Gordon)在这里对此进行了详细介绍。在MSDN上也有一篇文章。

如何使用string.Create

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

该方法具有三个参数:

  1. 要创建的字符串的长度,
  2. 您要用来动态创建新字符串的数据,
  3. 以及一个从数据创建最终字符串的委托,其中第一个参数指向char新字符串的内部数组,第二个参数是您传递给的数据(状态)string.Create

在委托内部,我们可以指定如何从数据创建新字符串。在我们的例子中,我们只是将输入字符串的字符复制到Span新字符串使用的字符上。然后我们将Span,因此整个字符串都反转了。

基准测试

为了将我提出的反转字符串的方法与可接受的答案进行比较,我使用BenchmarkDotNet编写了两个基准测试。

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 reverse快得多
code4j

7

不用理会功能,只需就地执行即可。注意:第二行将在某些VS版本的“即时”窗口中引发参数异常。

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

1
有些人花时间对每个答案(包括我的答案)投下反对票,却没有解释原因。
Marcel Valdez Orozco

由于您要创建一个new string
mbadawi23 '18

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:87毫秒
  • ReverseUsingStringBuilder:824ms
  • ReverseUsingStack:2086ms
  • ReverseUsingXOR:319ms

我在帖子中添加了类似的比较,它是社区Wiki,因此您应该可以进行编辑。性能实际上取决于字符串的长度以及算法,对它进行图形化将很有趣。我仍然认为Array.Reverse在所有情况下都将是最快的……
萨姆·

当神奇的TrySZReverse函数(在Reverse实现中使用)失败时,“在所有情况下都是最快的”,Array.Reverse回退到涉及拳击的简单实现,因此我的方法将获胜。但是我不知道使TrySZReverse失败的条件是什么。
aku

事实证明,它并非在所有情况下都最快:),我更新了我的帖子。仍然需要使用unicode来测试其正确性和速度。
山姆番红花

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

这没有用。
user3613932

3

怎么样:

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

与上述其他方法具有相同的代码点问题,并且执行速度会比ToCharArray第一次执行时慢得多。LINQ枚举器也比慢Array.Reverse()
亚伯(Abel)

3

我已经从Microsoft.VisualBasic.Strings创建了一个C#端口。我不确定为什么它们会在Framework的System.String之外但仍在Microsoft.VisualBasic下保留这样有用的功能(来自VB)。财务职能的相同场景(例如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,不错的补充!我只是用尝试过string s = "abo\u0327\u0307\u035d\U0001d166cd",它包含字母,o后跟BMP中的3个组合变音标记和一个来自星体平面(非BMP)的组合标记(MUSICAL SYMBOL COMBINING STEM),并保持它们不变。但是,如果此类字符仅出现在长字符串的末尾,则该方法将很慢,因为它必须在整个数组中重复两次。
亚伯(Abel)

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个字符比我的版本x 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,我认为这可能是最快的方法之一。它不会创建新的字符串,并且仅迭代数组的一半以上(即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'9

Big-O表示法中,不使用不依赖x或在您的情况下为的因数n。您的算法具有性能f(x) = x + ½x + C,其中C是一些常数。由于两者C和因素都不依赖x,因此您的算法是O(x)。这并不意味着对于任何长度的输入都不会更快x,但是其性能与输入长度线性相关。要回答@MarcelValdezOrozco,是的,它也是O(n),尽管它每16字节块复制一次以提高速度(它memcpy在总长度上不使用直线)。
亚伯(Abel)

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 []时有一个初始内存分配,而当字符串构造函数从char数组构建字符串时有一个分配。

在我的系统上,我为您运行了一个测试,该测试将一个2 750 000个字符的字符串反转。这是10次执行的结果:

StringBuilder:190K-200K滴答

字符数组:130K-160K ticks

我也对正常的String + =进行了测试,但10分钟后我放弃了它,但没有输出。

但是,我还注意到,对于较小的字符串,StringBuilder速度更快,因此您将不得不根据输入来决定实现。

干杯


不适用于😀Les Misérables
Charles

@Charles Ah yea,我想是字符集限制。
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);
        }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.