【问题标题】:Fastest way to sum digits in a number将数字中的数字相加的最快方法
【发布时间】:2011-03-11 17:53:35
【问题描述】:

给定一个很大的数字,例如9223372036854775807 (Int64.MaxValue),求和的最快方法是什么?

目前我正在 ToStringing 并将每个字符重新解析为 int

num.ToString().Sum(c => int.Parse(new String(new char[] { c })));

这肯定是非常低效的。有什么建议吗?

最后,您将如何使用 BigInteger 进行这项工作?

谢谢

【问题讨论】:

  • 这被称为数字的“面值”,顺便说一句。
  • 我们是否必须允许最大值为 MAX_INT?
  • 如果你真正想要的是repeated sum,那么有一个窍门。看到这个最近的问题:stackoverflow.com/questions/5151224/…

标签: c# math sum digits


【解决方案1】:

嗯,另一种选择是:

int sum = 0;
while (value != 0)
{
    int remainder;
    value = Math.DivRem(value, 10, out remainder);
    sum += remainder;
}

BigInteger 也有一个DivRem 方法,因此您可以使用相同的方法。

请注意,我发现DivRem 的速度不如“手动”执行相同的算术快,因此如果您真的对速度感兴趣,您可能需要考虑这一点。

还考虑一个包含(例如)1000 个元素的查找表,其中预先计算了总和:

int sum = 0;
while (value != 0)
{
    int remainder;
    value = Math.DivRem(value, 1000, out remainder);
    sum += lookupTable[remainder];
}

这意味着更少的迭代,但每次迭代都有一个额外的数组访问......

【讨论】:

  • 再一次,这也是 BigInteger.ToString() 必须做的事情。难以击败,它经过大量优化。
  • 如果DivRem比手动操作慢,有什么用?它不是更具可读性...
  • @configurator:很有可能它只是在某些情况下比较慢。老实说,我没有详细调查。
  • 我使用 DivRem 和整数除以 10 乘以 10 再减去进行了一些测试,DivRem 的速度也一样快。我还测试了使用 floor 乘以 0.10 * 10,但速度较慢。
【解决方案2】:

没有人讨论过 BigInteger 版本。为此,我会查看 101、102、104、108 等等,直到您发现最后 102n 小于您的值。取您的数字 div 和 mod 102n 得出 2 个较小的值。清洗、漂洗并递归地重复。 (您应该将 10 的迭代平方保留在一个数组中,并在递归部分传递有关下一个要使用的幂的信息。)

对于具有 k 位数字的 BigInteger,除以 10 是 O(k)。因此,用朴素算法求数字的总和是 O(k2)。

我不知道 C# 内部使用什么,但是用于将 k 位乘以或除以 k 位整数的非天真的算法都可以在 O(k1.6 ) 或更好(大多数都好得多,但是对于“小大整数”来说,开销会使它们变得更糟)。在这种情况下,准备你的初始权力列表并分裂一次需要时间 O(k1.6)。这给了你 2 个大小为 O((k/2)1.6) = 2-0.6O(k1.6) 的问题。在下一个级别,您有 4 个大小为 O((k/4)1.6) 的问题,另外 2-1.2O(k1.6)工作。将所有项加起来,2 的幂变成一个收敛到一个常数的几何级数,所以总功为 O(k1.6)。

这是一场绝对的胜利,如果您使用数千位数的数字,胜利将非常非常明显。

【讨论】:

    【解决方案3】:

    是的,它可能有点低效。我可能只是反复除以 10,每次都将余数相加。

    【讨论】:

      【解决方案4】:

      性能优化的第一条规则:可以乘法时不要除法。以下函数将采用四位数字 0-9999 并按照您的要求进行操作。中间计算大于 16 位。我们将该数字乘以 1/10000,并将结果作为 Q16 定点数。然后通过乘以 10 并取整数部分来提取数字。

      #define TEN_OVER_10000 ((1<<25)/1000 +1) // .001 Q25
      
      int sum_digits(unsigned int n)
      {
          int c;
          int sum = 0;
          n = (n * TEN_OVER_10000)>>9; // n*10/10000 Q16
          for (c=0;c<4;c++)
          {
          printf("Digit: %d\n", n>>16);
              sum += n>>16;
              n = (n & 0xffff) * 10; // next digit
          }
          return sum;
      }
      

      这可以扩展到更大的尺寸,但它很棘手。您需要确保定点计算中的舍入始终正常工作。我也做了4位数字,所以定点乘法的中间结果不会溢出。

      【讨论】:

        【解决方案5】:
            Int64 BigNumber = 9223372036854775807;
        
            String BigNumberStr = BigNumber.ToString();
            int Sum = 0;
        
            foreach (Char c in BigNumberStr)
                Sum += (byte)c;
        
            // 48 is ascii value of zero
            // remove in one step rather than in the loop
            Sum -= 48 * BigNumberStr.Length;
        

        【讨论】:

          【解决方案6】:

          而不是int.parse,为什么不从每个数字中减去'0'以获得实际值。

          请记住,'9' - '0' = 9,因此您应该能够按照 k(数字的长度)的顺序执行此操作。减法只是一种运算,因此不会减慢速度。

          【讨论】:

          • 你在开玩笑吗?与将每个字符串数字转换为 int 不一样(实际上更糟)吗?
          • 如果你指的是 Integer.parseInt,那么没有。查看该方法的代码。 docjar.com/html/api/java/lang/Integer.java.html 处理了很多错误情况等。如果你知道你正在处理一个整数字符串,那么减法将比使用其他方法转换它们更快。
          • 我刚刚意识到上面的源代码是用于 Java,而不是 C#,但你明白了。
          猜你喜欢
          • 1970-01-01
          • 2020-01-20
          • 2018-02-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多