反转字符串的最佳方法
本文关键字:最佳 方法 字符串 | 更新日期: 2023-09-27 17:48:52
我只需要在 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;
}
就我个人而言,我对该功能并不疯狂,并且相信有更好的方法可以做到这一点。有吗?
public static string Reverse( string s )
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
这里有一个正确反转字符串的解决方案"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 进行字形簇迭代,它从一开始就存在,但似乎有点"隐藏"在视图中。
这是一个令人惊讶的棘手问题。
我建议在大多数情况下使用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 似乎可以更快。
如果你可以使用LINQ (.NET Framework 3.5+(,那么遵循一行代码会给你短代码。不要忘记添加using System.Linq;
以访问Enumerable.Reverse
:
public string ReverseString(string srtVarable)
{
return new string(srtVarable.Reverse().ToArray());
}
笔记:
- 不是最快的版本 - 根据马丁·尼德尔的说法,比这里最快的选择慢 5.7 倍。
- 此代码与许多其他选项一样完全忽略了各种多字符组合,因此将使用限制为不包含此类字符的家庭作业和字符串。请参阅此问题中的另一个答案,了解正确处理此类组合的实现。
如果字符串包含 Unicode 数据(严格来说,非 BMP 字符(,则已发布的其他方法将损坏它,因为在反转字符串时无法交换高代理项和低代理项代码单元的顺序。(有关此内容的更多信息可以在我的博客上找到。
下面的代码示例将正确反转包含非 BMP 字符的字符串,例如,"''U00010380''U00010381"(乌加里特字母阿尔帕,乌加里特字母测试版(。
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);
}
好的,为了"不要重复自己",我提供以下解决方案:
public string Reverse(string text)
{
return Microsoft.VisualBasic.Strings.StrReverse(text);
}
我的理解是,此实现(在 VB.NET 中默认可用(可以正确处理 Unicode 字符。
请在此处查看维基百科条目。它们实现 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);
}
从 .NET Core 2.1 开始,有一种使用 string.Create
方法反转字符串的新方法。
请注意,此解决方案不能正确处理组合字符等的 Unicode,因为"Les Mise''u0301rables"将被转换为"selbarésiM seL"。请参阅其他答案以获得更好的解决方案。
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);
该方法采用三个参数:
- 要创建的字符串的长度,
- 要用于动态创建新字符串的数据,
- 以及从数据创建最终字符串的委托,其中第一个参数指向新字符串的内部
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
方法使用的内存的一半。
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
方法消失。但是,对于中小型琴弦,很难击败这种方法。
简单而好的答案是使用扩展方法:
static class ExtentionMethodCollection
{
public static string Inverse(this string @base)
{
return new string(@base.Reverse().ToArray());
}
}
这是输出:
string Answer = "12345".Inverse(); // = "54321"
如果你想玩一个非常危险的游戏,那么这是迄今为止最快的方法(比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;
}
}
首先,您不需要调用ToCharArray
因为字符串已经可以索引为 char 数组,因此这将为您节省分配。
下一个优化是使用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
方法既简单又快速,我建议您使用它而不是此方法。我把这个留在这里只是为了表明这不是你应该这样做的方式(让我感到惊讶!
尝试使用 Array.Reverse
public string Reverse(string str)
{
char[] array = str.ToCharArray();
Array.Reverse(array);
return new string(array);
}
最佳"可以取决于很多事情,但这里有一些从快到慢排序的简短替代方案:
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̽"
public static string Reverse(string input)
{
return string.Concat(Enumerable.Reverse(input));
}
当然你可以用反向方法扩展字符串类
public static class StringExtensions
{
public static string Reverse(this string input)
{
return string.Concat(Enumerable.Reverse(input));
}
}
抱歉长帖子,但这可能很有趣
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");
}
}
}
结果:
- 反向使用字符缓冲区:346ms
- 反向使用数组类:87ms
- 反向使用字符串生成器:824ms
- 反向使用堆栈:2086ms
- 反向使用异或:319ms
函数而烦恼,只需就地执行即可。 注意:第二行将在某些VS版本的"即时"窗口中引发参数异常。
string s = "Blah";
s = new string(s.ToCharArray().Reverse().ToArray());
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
最简单的方法:
string reversed = new string(text.Reverse().ToArray());
更好的方法"取决于您的情况、性能、优雅、可维护性等对您更重要。
无论如何,这里有一个使用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);
怎么样:
private string Reverse(string stringToReverse)
{
char[] rev = stringToReverse.Reverse().ToArray();
return new string(rev);
}
基于堆栈的解决方案。
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);
}
我从Microsoft.VisualBasic.Strings创建了一个C#端口。我不确定为什么他们将这些有用的函数(来自VB(保留在框架中的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();
}
必须提交一个递归示例:
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));
}
由于我喜欢几个答案 - 一个用于使用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
如果在面试中提到它,并且你被告知你不能使用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);
}
很抱歉在这个旧线程上发帖。我正在为面试练习一些代码。
这就是我想出的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 个字符,我会使用我制作的字符串。
测试是通过 Stopwatch
次和 5000000 次迭代完成的。另外,我不确定我的版本是否处理代理项或Unicode
编码的组合字符情况。
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);
}
如果字符串仅包含 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);
}
首先,您必须了解的是,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 次执行的结果:
字符串生成器: 190K - 200K 刻度
字符数组:130K - 160K 刻度
我还对普通字符串 += 进行了测试,但在 10 分钟后放弃了它,没有输出。
但是,我还注意到,对于较小的字符串,StringBuilder 更快,因此您必须根据输入来决定实现。
干杯