【问题标题】:Quickest Method to Reverse in String in C#.net在 C#.net 中反转字符串的最快方法
【发布时间】:2009-01-11 17:02:11
【问题描述】:

我目前正在为欧拉问题 #4 编写一个快速解决方案,其中必须从两个 3 位数字的乘积中找到最大的回文数。

要确定一个数字是否是回文,您显然需要将该数字的倒数与原始数字进行比较。

由于 C# 没有内置的 String.Reverse() 方法,那么反转字符串最快的方法是什么?

我将使用 100,000,000 次迭代循环测试所有建议的解决方案。正确答案将给予提交最快解决方案的人。

我将在 C#.Net 3.5 控制台应用程序中测试该解决方案

【问题讨论】:

  • 您可能过度指定了解决方案,尤其是因为您的字符串太小了。为什么不要求一种方法来比较两个整数,如果它们是回文则返回 true?

标签: c#


【解决方案1】:

反转数字不是更快吗?

// unchecked code, don't kill me if it doesn't even compile.
ulong Reverse(ulong number) {
    ulong result = 0;

    while (number > 0) {
        ulong digit = number % 10;
        result = result * 10 + digit;
        number /= 10;
    }

    return result;
}

【讨论】:

  • +1,这比将数字转换为字符串、将其反转并进行字符串比较要高效得多。
  • 您的建议耗时 34secs 249ms
  • @configurator:感谢您的努力 :) 我接受了 P Daddy 的代码,因为它专门回答了原始问题。此外,ggf31416 的 FastReverse 功能要快得多。
  • 配置器:您的赞成票比接受的答案多。这不言自明。 ;]
  • 任何数字模块 10 是它的最后一位数字 - 所以digit 将永远是number 的最后一位数字。
【解决方案2】:

如果您想将一个数字与其相反的数字进行比较,使用除法来反转数字可能比将其转换为字符串更快。我还需要测试它的速度。

 private static int Reverse(int num) {
     int res = 0;
     while (num > 0) {
        int rm ;
        num = Math.DivRem(num, 10, out rm);
        res = res * 10 + rm;
     }
     return res;
  }

编辑: DivRem 比我计算机中的除法和模块快约 1%。 最后一位为0则退出速度优化:

  private static int Reverse(int num) {
     int res = 0;
     int rm;
     num = Math.DivRem(num, 10, out rm);
     //Some magic value or return false, see below.
     if (rm == 0) return -1 ; 
     res = res * 10 + rm;
     while (num > 0) {
        num = Math.DivRem(num, 10, out rm);
        res = res * 10 + rm;
     }
     return res ;
  }

在我的计算机中,使方法返回布尔值比与循环中的布尔值相比要慢一些,但我不明白为什么。请在您的计算机上进行测试。

乘法和移位应该比除法更快,但可能不够精确。编辑:使用 long 似乎足够精确。

  private static int FastReverse(int num) {
     int res = 0;
     int q = (int)((214748365L * num) >> 31);
     int rm = num - 10 * q;
     num = q;
     if (rm == 0) return -1;
     res = res * 10 + rm;
     while (num > 0) {
        q = (int)((214748365L * num) >> 31);
        rm = num - 10 * q;
        num = q;
        res = res * 10 + rm;
     }
     return res;
  }

(214748365L * num) >> 31 等于 i / 10 直到 1,073,741,829,其中 1 / 10 得到 107374182,乘法 + 二进制移位得到 107374183。

【讨论】:

  • 我不知道 Math.DivRem。手感不错。
  • 你的第一个建议用了 36 秒 816 毫秒,你的第二个建议用了 34 秒 74 毫秒
  • 天啊!您的第三个建议耗时 13 秒 916 毫秒!
  • 好招!对于那些不明白为什么会这样的人,“>> 31”相当于除以 2,147,483,648。因此,乘以 214,748,365 然后除以 2,147,483,648 产生与除以 10 相同的结果(感谢整数截断),但速度要快得多,因为除法很慢。
【解决方案3】:

我认为就地进行比较可能会更快。如果你反转字符串,你必须:

  1. 实例化一个新的字符串对象(或 StringBuffer 对象)
  2. 将第一个字符串中的数据(反向)复制到新字符串中
  3. 进行比较。

如果您在原地执行比较,则只执行最后一步。即便如此,您的比较只是字符串的一半(如果字符数为奇数,则为一半 - 0.5)。像下面这样的东西应该可以工作:

static bool IsPalindromic(string s){
    int len = s.Length;
    int half = len-- >> 1;
    for(int i = 0; i < half; i++)
        if(s[i] != s[len - i])
            return false;
    return true;
}

编辑:

虽然这回答了 OP 的问题,但根据我的测试,ggf31416configurator 提供的解决方案解决了 OP 的实际需求大约快 30%。如果将其转换为静态方法并使用ints 而不是ulongs,则配置器的解决方案比 ggf31416 的解决方案快一点(但要慢得多,否则)。


顺便说一句,运行这些示例来解决 OP 提到的问题(找到任何两个三位数的最大回文乘积),使用下面的简单(可能是幼稚的)循环:

for(int i = 100; i < 1000; i++)
    for(int j = i; j < 1000; j++) // calculations where j < i would be redundant
        ...

在我的机器上产生以下结果:

IsPalindromic(product.ToString()) 耗时 0.3064174 秒。 ggf31416Reverse(product) == product 耗时 0.1933994 秒。 configuratorReverse(product) == product 耗时 0.1872061 秒。

每个都产生913 * 993 = 906609的正确结果。

【讨论】:

  • 您的建议耗时 49secs 435ms
【解决方案4】:
【解决方案5】:
string test = "ABC";
string reversed = new String(test.ToCharArray().Reverse().ToArray());

【讨论】:

  • 您的建议耗时 1 分 51 秒 608 毫秒
【解决方案6】:
public static String Reverse(string input) {
  var length = input.Length;
  var buffer = new char[length];
  for ( var i= 0; i < input.Length; i++ ) {
    buffer[i] = input[(length-i)-1];
  }
  return new String(buffer);
}

编辑:Doh!忘记将 perf 的长度减半 :)

【讨论】:

  • 您的建议耗时 1 分 49 秒 919 毫秒
【解决方案7】:

我发现在 C# 中反转字符串的最快方法是使用以下代码。一次读取 32 位而不是 16 位字符的长度更快。 在调试模式下,它会更快,直到您达到大约 93 个字符。比 Array.Reverse() 更长的任何东西都更快。使用发布版本并在 IDE 之外运行,此方法将以任何字符串长度将 Array.Reverse() 从水中吹走。

char[] MyCharArray = MyString.ToCharArray();
UIntStringReverse(ref MyCharArray);     //Code to reverse is below.
string ReversedString = new string(MyCharArray);


private static unsafe void UIntStringReverse(ref char[] arr)
{
    uint Temp;
    uint Temp2;

    fixed (char* arrPtr = &arr[0])
    {
        uint* p, q;
        p = (uint*)(arrPtr);
        q = (uint*)(arrPtr + arr.LongLength - 2);

        if (arr.LongLength == 2)
        {
            Temp = *p;
            *p = ((Temp & 0xFFFF0000) >> 16) | ((Temp & 0x0000FFFF) << 16); 
            return;
        }

        while (p < q)
        {
            Temp = *p;
            Temp2 = *q;

            *p = ((Temp2 & 0xFFFF0000) >> 16) | ((Temp2 & 0x0000FFFF) << 16); 
            *q = ((Temp & 0xFFFF0000) >> 16) | ((Temp & 0x0000FFFF) << 16);

            p++;
            q--;
        }
    }
}

【讨论】:

    【解决方案8】:
    【解决方案9】:
    string Reverse(string s)
    {
      return new string(s.ToCharArray().Reverse().ToArray());
    }
    

    【讨论】:

    • 虽然不是性能最高的,但它是最小且最容易编写的。
    【解决方案10】:

    使用 ggf31416 的 FastReverse 函数,这里是 Project Euler 的问题 #4 的解决方案,它在我的计算机上在 47 毫秒内完成。

    using System;
    using System.Diagnostics;
    
    namespace Euler_Problem_4
    {
        class Program
        {
            static void Main(string[] args)
            {
                Stopwatch s = new Stopwatch();
                s.Start();
    
                int t = 0;
    
                for (int i = 999; i > 99; i--)
                {
                    for (int j = i; j > 99; j--)
                    {
                        if (i*j == FastReverse(i*j))
                        {
                            if (i * j > t)
                            {
                                t = i * j;
                            }
                        }
                    }
                }
    
                Console.WriteLine(t);
    
                s.Stop();
                Console.WriteLine("{0}mins {1}secs {2}ms", s.Elapsed.Minutes, s.Elapsed.Seconds, s.Elapsed.Milliseconds);
                Console.ReadKey(true);
    
            }
    
            private static int FastReverse(int num)
            {
                int res = 0;
                int q = (int)((214748365L * num) >> 31);
                int rm = num - 10 * q;
                num = q;
                if (rm == 0) return -1;
                res = res * 10 + rm;
                while (num > 0)
                {
                    q = (int)((214748365L * num) >> 31);
                    rm = num - 10 * q;
                    num = q;
                    res = res * 10 + rm;
                }
                return res;
            }
        }
    }
    

    【讨论】:

      【解决方案11】:

      Stopwatch 类需要在每次运行后重置。以下代码已更正

      var d = s.ToCharArray();
      Array.Reverse(d);
      return s == new string(d);
      

      using System;
      using System.Diagnostics;
      
      namespace longeststring_codegolf
      {
          class Program
          {
              static void Main(string[] args)
              {
                  int t = 0, v = 0;
                  var sw = new Stopwatch();
      
                  sw.Start();
                  for (int i = 999; i > 99; i--)
                      for (int j = 999; j > 99; j--)
                          if ((v = i * j) > t && IsPalindromicMine(v.ToString()))
                              t = v;
                  sw.Stop();
      
                  var elapsed = sw.Elapsed;
                  var elapsedMilliseconds = sw.ElapsedMilliseconds;
                  var elapsedTicks = sw.ElapsedTicks; 
                  Console.WriteLine("Ticks: " + elapsedTicks.ToString());//~189000
                  Console.WriteLine("Milliseconds: " + elapsedMilliseconds.ToString()); //~9
      
                  sw = Stopwatch.StartNew();
                  for (int i = 999; i > 99; i--)
                      for (int j = 999; j > 99; j--)
                          if ((v = i * j) > t && IsPalindromic(v.ToString()))
                              t = v;
                  sw.Stop();
                  var elapsed2 = sw.Elapsed;
                  var elapsedMilliseconds2 = sw.ElapsedMilliseconds;
                  var elapsedTicks2 = sw.ElapsedTicks; 
                  Console.WriteLine("Ticks: " + elapsedTicks2.ToString());//~388000
                  Console.WriteLine("Milliseconds: " + elapsedMilliseconds2.ToString());//~20
      
              }
      
              static bool IsPalindromicMine(string s)
              {
                  var d = s.ToCharArray();
                  Array.Reverse(d);
                  return s == new string(d);
              }
      
              static bool IsPalindromic(string s)
              {
                  int len = s.Length;
                  int half = len-- >> 1;
                  for (int i = 0; i < half; i++)
                      if (s[i] != s[len - i])
                          return false;
                  return true;
              }
      
          }
      }
      

      【讨论】:

      • 我不同意。一些人已经提出了您的上述解决方案,实际上是最慢的!
      • 是的,我知道计时器的问题在于我没有重置它(我假设调用 start 会重置它)他的胜利主要是因为它只需要测试一半的字符串。所以它平均快两倍。 (但看到我的答案是一样的......如果不是比使用 Reverse() 的 linq 版本的版本更快,我不明白为什么我会投反对票)
      猜你喜欢
      • 1970-01-01
      • 2017-12-09
      • 1970-01-01
      • 2021-06-25
      • 2019-03-14
      • 2019-03-06
      • 1970-01-01
      • 2011-03-20
      相关资源
      最近更新 更多