洗牌字符

问题描述

我需要以这样的方式打乱字符:在每次迭代时,将字符串的奇数字符组合并包裹到其开头,将偶数字符包裹到末尾。

public static string ShuffleChars(string source,int count)
    {
        if (string.IsNullOrEmpty(source))
        {
            throw new ArgumentException("source is null or empty");
        }

        if (string.IsNullOrWhiteSpace(source))
        {
            throw new ArgumentException("source is white space");
        }

        if (count < 0)
        {
            throw new ArgumentException("count < 0");
        }

        for (int j = 0; j < count; j++)
        {
            string tempOdd = string.Empty;
            string tempEven = string.Empty;
            for (int i = 0; i < source.Length; i++)
            {
                if (i % 2 == 0)
                {
                    tempOdd += source[i];
                }
                else if (i % 2 != 0)
                {
                    tempEven += source[i];
                }
            }

            source = tempOdd + tempEven;
        }

        return source;
    }

这工作得很好但是,当 count = int.MaxValue 时,它​​似乎处于无休止的加载中 给我的任务说我必须优化它,人们建议使用 StringBuilder,所以我想出了这样的东西:

public static string ShuffleChars(string source,int count)
    {
        if (string.IsNullOrEmpty(source))
        {
            throw new ArgumentException("source is null or empty");
        }

        if (string.IsNullOrWhiteSpace(source))
        {
            throw new ArgumentException("source is white space");
        }

        if (count < 0)
        {
            throw new ArgumentException("count < 0");
        }

        StringBuilder sourceString = new StringBuilder(source);
        StringBuilder tempOdd = new StringBuilder(string.Empty);
        StringBuilder tempEven = new StringBuilder(string.Empty);
        for (int j = 0; j < count; j++)
        {
            tempOdd.Clear();
            tempEven.Clear();
            for (int i = 0; i < sourceString.Length; i++)
            {
                if (i % 2 == 0)
                {
                    tempOdd.Append(sourceString[i]);
                }
                else
                {
                    tempEven.Append(sourceString[i]);
                }
            }

            sourceString = tempOdd.Append(tempEven);
        }

        return sourceString.ToString();
    }

据我所知,当我清除 tempOdd 和 tempEven 时,sourceString 也会被清除,这就是为什么当我多次洗牌字符串时,它会返回空字符串。 可能还有其他优化方法吗?

解决方法

问题在于您正在设置 sourceString = tempOdd.Append(tempEven);。即,sourceString 现在是指向与 StringBuilder 相同的 tempOdd 对象的引用!然后您正在清除 tempOdd,它实际上是与 sourceString 相同的对象。顺便说一句,你颠倒了偶数和奇数。 i % 2 == 0 是偶数。

相反,在清除奇数和偶数字符串后,将其附加到 sourceString

sourceString.Clear();
sourceString.Append(tempOdd).Append(tempEven);

请注意,Append 返回 StringBuilder 本身。因此,这相当于

sourceString.Clear();
sourceString.Append(tempOdd);
sourceString.Append(tempEven);

字符串是不可变的。因此,当您操作字符串时,您总是在创建新字符串。例如,当您向 tempOdd 添加一个字符时,这会创建一个长度长一个字符的新字符串对象。然后它将旧字符串复制到新字符串中并附加字符。这会生成大量新对象并涉及大量复制。

StringBuilder 使用内部可变缓冲区。由于这些缓冲区的大小在每次迭代时都保持不变,因此可以将字符附加到已经存在的缓冲区中,而无需创建对象(初始化阶段除外)和复制字符串。

因此 StringBuilderstring 更有效率。


但是正如@JL0PD 已经指出的那样,您可以进行更多优化。偶数和奇数部分的长度是预先知道的。因此,我们可以将字符复制到最后的位置,从而避免在最后连接结果。

此外,该解决方案在每次迭代时重用相同的字符缓冲区。为了实现这一点,我们必须在每次迭代时交换两个缓冲区,使之前的结果成为新的源。

public static string ShuffleChars(string source,int count)
{
    if (string.IsNullOrWhiteSpace(source)) {
        throw new ArgumentException("source is null or empty or white space");
    }
    if (count < 0) {
        throw new ArgumentException("count < 0");
    }

    // Initialize the wrong way,since we are swapping later.
    var resultChars = source.ToCharArray();
    var sourceChars = new char[source.Length];
    for (int j = 0; j < count; j++) {
        // Swap source and result. This enables us to reuse the same buffers.
        var temp = sourceChars;
        sourceChars = resultChars;
        resultChars = temp;

        // We don't need to clear,since we fill every character position anyway.
        int iOdd = 0;
        int iEven = source.Length / 2;
        for (int i = 0; i < source.Length; i++) {
            if (i % 2 == 0) {
                resultChars[iEven++] = sourceChars[i];
            } else {
                resultChars[iOdd++] = sourceChars[i];
            }
        }
    }

    return new String(resultChars);
}