【问题标题】:How to sum up an array of integers in C#如何在 C# 中对整数数组求和
【发布时间】:2011-01-26 00:50:57
【问题描述】:

有没有比遍历数组更短的更好方法?

int[] arr = new int[] { 1, 2, 3 };
int sum = 0;
for (int i = 0; i < arr.Length; i++)
{
    sum += arr[i];
}

澄清:

更好的主要意味着更简洁的代码,但也欢迎提供有关性能改进的提示。 (就像已经提到的:拆分大型数组)。


这不像是我在寻找杀手级性能改进 - 我只是想知道这种 语法糖 是否已经不可用:“有 String.Join - int[] 到底是什么鬼?”。

【问题讨论】:

  • 以什么方式更好?快点?写的代码少?

标签: c# arrays integer


【解决方案1】:

如果您可以使用 .NET 3.5(或更高版本)和 LINQ,请尝试

int sum = arr.Sum();

【讨论】:

  • 值得注意的是,如果结果大于带符号的 32 位整数(即 (2^31) -1 或英文),则会引发错误 System.OverflowException约 21 亿)。
  • int sum = arr.AsParallel().Sum(); 使用多个 CPU 内核的更快版本。为避免System.OverflowException,您可以使用long sum = arr.AsParallel().Sum(x =&gt; (long)x); 对于避免溢出异常并支持所有整数数据类型并使用数据并行 SIMD/SSE 指令的更快版本,请查看 HPCsharp nuget 包
【解决方案2】:

是的,有。使用 .NET 3.5:

int sum = arr.Sum();
Console.WriteLine(sum);

如果您不使用 .NET 3.5,您可以这样做:

int sum = 0;
Array.ForEach(arr, delegate(int i) { sum += i; });
Console.WriteLine(sum);

【讨论】:

  • 为什么会有如此复杂的 3.5 前版本? foreach 循环在所有 C# 版本中都可用。
  • @Jørn:OP 要求采用更短的方法。 foreach 只是将一行代码替换为另一行代码,并且不会更短。除此之外,foreach 非常好并且更具可读性。
  • 点了。然而,与您的样本相比,以下内容节省了 18 个字符:foreach (int i in arr) sum += i;
【解决方案3】:

使用 LINQ:

arr.Sum()

【讨论】:

    【解决方案4】:

    这取决于您如何更好地定义。如果您希望代码看起来更干净,您可以使用其他答案中提到的 .Sum() 。如果您希望操作快速运行并且您有一个大数组,您可以通过将其分解为子和然后对结果求和来使其并行。

    【讨论】:

    • +1 在性能改进方面非常好,但老实说,我最初的愿望是摆脱迭代。
    • (没有人告诉 Fil 他只是将迭代向下推了几级)
    • @Will:老兄——别指望我相信如果我不写代码魔法就会发生;-)
    • 我怀疑它必须是一个非常大的数组才能使这种并行优化有意义。
    • 是的,从什么时候开始 for 循环变得不好了?
    【解决方案5】:

    使用 foreach 代码会更短,但在 JIT 优化识别出与 for 循环控制表达式中的 Length 比较后,可能会在运行时执行完全相同的步骤。

    【讨论】:

      【解决方案6】:

      如果你不喜欢 LINQ,最好使用 foreach 循环来避免超出索引。

      int[] arr = new int[] { 1, 2, 3 };
      int sum = 0;
      foreach (var item in arr)
      {
         sum += item;
      }
      

      【讨论】:

        【解决方案7】:

        在我使用的一个应用中:

        public class ClassBlock
        {
            public int[] p;
            public int Sum
            {
                get { int s = 0;  Array.ForEach(p, delegate (int i) { s += i; }); return s; }
            }
        }
        

        【讨论】:

        • 这与使用.Aggregate()扩展方法相同。
        【解决方案8】:

        上述for循环解决方案的一个问题是,对于以下所有正值的输入数组,求和结果为负:

        int[] arr = new int[] { Int32.MaxValue, 1 };
        int sum = 0;
        for (int i = 0; i < arr.Length; i++)
        {
            sum += arr[i];
        }
        Console.WriteLine(sum);
        

        总和为 -2147483648,因为正数结果对于 int 数据类型而言太大,溢出为负值。

        对于相同的输入数组,arr.Sum() 建议会引发溢出异常。

        一个更健壮的解决方案是使用更大的数据类型,例如本例中的“long”,用于“sum”,如下所示:

        int[] arr = new int[] { Int32.MaxValue, 1 };
        long sum = 0;
        for (int i = 0; i < arr.Length; i++)
        {
            sum += arr[i];
        }
        

        同样的改进也适用于其他整数数据类型的求和,例如 short 和 sbyte。对于 uint、ushort 和 byte 等无符号整数数据类型的数组,使用无符号长整数 (ulong) 进行求和可避免溢出异常。

        for循环解决方案也比Linq.Sum()快很多倍

        为了更快地运行,HPCsharp nuget 包实现了所有这些 .Sum() 版本以及 SIMD/SSE 版本和多核并行版本,性能提高了很多倍。

        【讨论】:

        • 好主意。而且,对于无符号整数数组,能够做到 ulong sum = arr.Sum(x => (ulong)x); 会很好。但是,遗憾的是,Linq .Sum() 不支持无符号整数数据类型。如果需要无符号求和,HPCsharp nuget 包支持所有无符号数据类型。
        • 一位贡献者撤回了 long sum = arr.Sum(x =&gt; (long)x); 的一个好主意,它在 C# 中使用 Linq 很好地工作。它为所有有符号整数数据类型(sbyte、short 和 int)的求和提供了完整的准确性。它还避免引发溢出异常,并且非常紧凑。它的性能不如上面的 for 循环,但并非所有情况都需要性能。
        【解决方案9】:

        另一种方法是使用Aggregate() 扩展方法。

        var sum = arr.Aggregate((temp, x) => temp+x);
        

        【讨论】:

        • 这似乎适用于 Sum 不适用的地方。由于某种原因,它不适用于 uint 数组,但 Aggregate 可以。
        【解决方案10】:

        对于非常大的阵列,使用机器的多个处理器/内核执行计算可能会有所回报。

        long sum = 0;
        var options = new ParallelOptions()
            { MaxDegreeOfParallelism = Environment.ProcessorCount };
        Parallel.ForEach(Partitioner.Create(0, arr.Length), options, range =>
        {
            long localSum = 0;
            for (int i = range.Item1; i < range.Item2; i++)
            {
                localSum += arr[i];
            }
            Interlocked.Add(ref sum, localSum);
        });
        

        【讨论】:

          【解决方案11】:

          对 Theodor Zoulias 出色的多核 Parallel.ForEach 实现的改进:

              public static ulong SumToUlongPar(this uint[] arrayToSum, int startIndex, int length, int degreeOfParallelism = 0)
              {
                  var concurrentSums = new ConcurrentBag<ulong>();
          
                  int maxDegreeOfPar = degreeOfParallelism <= 0 ? Environment.ProcessorCount : degreeOfParallelism;
                  var options = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfPar };
          
                  Parallel.ForEach(Partitioner.Create(startIndex, startIndex + length), options, range =>
                  {
                      ulong localSum = 0;
                      for (int i = range.Item1; i < range.Item2; i++)
                          localSum += arrayToSum[i];
                      concurrentSums.Add(localSum);
                  });
          
                  ulong sum = 0;
                  var sumsArray = concurrentSums.ToArray();
                  for (int i = 0; i < sumsArray.Length; i++)
                      sum += sumsArray[i];
          
                  return sum;
              }
          

          适用于无符号整数数据类型,因为 C# 仅支持 Int 和 long 的 Interlocked.Add()。上述实现也可以轻松修改以支持其他整数和浮点数据类型使用 CPU 的多个内核并行进行求和。它用于 HPCsharp nuget 包中。

          【讨论】:

            猜你喜欢
            • 2015-05-02
            • 1970-01-01
            • 2021-12-09
            • 2017-10-24
            • 2021-09-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-12-15
            相关资源
            最近更新 更多