问题描述
我需要以这样的方式打乱字符:在每次迭代时,将字符串的奇数字符组合并包裹到其开头,将偶数字符包裹到末尾。
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
使用内部可变缓冲区。由于这些缓冲区的大小在每次迭代时都保持不变,因此可以将字符附加到已经存在的缓冲区中,而无需创建对象(初始化阶段除外)和复制字符串。
因此 StringBuilder
比 string
更有效率。
但是正如@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);
}