反转字符串的最佳方法

570

我刚刚不得不在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;
}

就我个人而言,我并不热衷于这个函数,并且确信有更好的方法来完成它。有吗?


63
如果您想获得适当的国际支持,翻译可能会非常棘手。例如,克罗地亚语/塞尔维亚语有两个字符的字母“lj”、“nj”等。 “ljudi”的正确反转应该是“idulj”,而不是“idujl”。当涉及到阿拉伯语、泰语等语言时,情况可能更加复杂。 - dbkk
2
我想知道将字符串连接起来是否比初始化临时数组并将结果存储在其中再将其转换为字符串更慢? - The Muffin Man
4
较新的相关帖子:如何反转带有重音字符的字符串? - Jeppe Stig Nielsen
15
请问您所说的“最好”是指什么?速度最快?可读性最高?在多种情况下(如空值检查、多语言等)最可靠?在不同版本的C#和.NET中最易维护? - hypehuman
5
为什么没有内置的直接方法来完成这个简单的任务? - Krishnadas PC
53个回答

4
"更好的方式" 取决于您在特定情况下更注重性能、优雅、可维护性等哪些方面。无论如何,以下是一种使用 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);

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

2
长度为0的字符串未被处理。 - bohdan_trotsenko

4

我从Microsoft.VisualBasic.Strings中翻译了一个C#版本。我不确定为什么他们把这些有用的函数(来自VB)放在Framework的System.String之外,但仍在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,一个不错的补充!我刚试了一下 string s = "abo\u0327\u0307\u035d\U0001d166cd",其中包含字母 o 后面跟着 BMP 中的 3 个组合变音符号和来自天界平面(非 BMP)的一个组合符号(MUSICAL SYMBOL COMBINING STEM),它可以保持它们的完整性。但是如果这样的字符仅出现在长字符串的末尾,则该方法速度较慢,因为它必须两次遍历整个数组。 - Abel

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

由于我喜欢其中一种答案-使用string.Create,因此具有高性能和低分配,另一种则是正确的-使用StringInfo类,我决定需要结合这两种方法。这是终极字符串反转方法 :)

private static string ReverseString(string str)
    {
        return string.Create(str.Length, str, (chars, state) =>
        {
            var enumerator = StringInfo.GetTextElementEnumerator(state);
            var position = state.Length;
            while (enumerator.MoveNext())
            {
                var cluster = ((string)enumerator.Current).AsSpan();
                cluster.CopyTo(chars.Slice(position - cluster.Length));
                position -= cluster.Length;
            }
        });
    }

使用StringInfo类的一种方法可以更好地实现,它通过返回索引来跳过遍历器产生的大量字符串分配。
private static string ReverseString(string str)
    {
        return string.Create(str.Length, str, (chars, state) =>
        {
            var position = 0;
            var indexes = StringInfo.ParseCombiningCharacters(state); // skips string creation
            var stateSpan = state.AsSpan();
            for (int len = indexes.Length, i = len - 1; i >= 0; i--)
            {
                var index = indexes[i];
                var spanLength = i == len - 1 ? state.Length - index : indexes[i + 1] - index;
                stateSpan.Slice(index, spanLength).CopyTo(chars.Slice(position));
                position += spanLength;
            }
        });
    }

与 LINQ 解决方案相比的一些基准测试结果:

String length 20:

LINQ                       Mean: 2,355.5 ns   Allocated: 1440 B
string.Create              Mean:   851.0 ns   Allocated:  720 B
string.Create with indexes Mean:   466.4 ns   Allocated:  168 B

String length 450:

LINQ                          Mean: 34.33 us   Allocated: 22.98 KB
string.Create                 Mean:   19.13 us   Allocated: 14.98 KB
string.Create with indexes    Mean:   10.32 us   Allocated: 2.69 KB

3
如果在面试中被告知不能使用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);
    }

3
我相当确定stringToReverse.ToCharArray()的调用将会产生O(N)的执行时间。 - Marcel Valdez Orozco
1
大O符号表示法中,与x无关的因子(在您的情况下是n)不会被使用。算法的性能为 f(x) = x + ½x + C,其中C是一些常数。由于C因子都不依赖于x,因此您的算法是O(x)。这并不意味着它不会对任何长度为x的输入更快,但其性能是线性依赖于输入长度的。回答@MarcelValdezOrozco,是的,它也是O(n),尽管它每次复制16字节块以提高速度(它不使用总长度的直接memcpy)。 - 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个字符时,这个版本比我的版本快4倍。然而,如果我知道字符串总是少于13个字符,我会使用我做的那个。

测试是使用Stopwatch和5000000次迭代完成的。另外,我不确定我的版本是否处理了使用Unicode编码的替代和组合字符情况。


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

2
首先,你需要明白的是str+=会调整字符串内存大小以为一个额外的字符腾出空间。这没问题,但如果你想要反转一本有1000页的书,执行这个操作将非常耗时。
一些人可能会建议使用StringBuilder。当你执行+=时,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滴答声
Char Array:130K-160K滴答声
我还进行了普通String +=的测试,但在10分钟内没有输出,我放弃了它。
然而,我也注意到,对于较小的字符串,StringBuilder更快,因此您将根据输入来决定实现方式。
干杯

@Charles 噢,我想这可能是字符集限制的问题。 - Reasurria

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接