如何在C#中对整数数组求和

146

除了迭代数组,还有更好的缩短方式吗?

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

澄清:

更好的主要方法是编写更干净的代码,但也欢迎有关性能改进的提示。(例如已经提到的:拆分大型数组)。


我并不是想寻求杀手级别的性能改进 - 我只是想知道这种语法糖是否已经可用:“已经有了String.Join - int[]怎么办?”。


2
更好在哪里?更快?更少的代码? - Fredrik Mörk
11个回答

248

只要您可以使用.NET 3.5(或更新版本)和LINQ,请尝试

int sum = arr.Sum();

示例

int[] arr = new int[] { 1, 2, 3 };
int sum = arr.Sum();

// output = 6
Console.WriteLine(sum);

15
值得注意的是,如果结果大于一个有符号32位整数所能表示的最大值(即2^31-1,约为21亿),则会引发“System.OverflowException”错误。 - ChrisProsser
6
int sum = arr.AsParallel().Sum(); 一种使用CPU多核心的更快的版本。为了避免 System.OverflowException 错误,您可以使用 long sum = arr.AsParallel().Sum(x => (long)x); 更快的版本不仅避免了溢出异常并支持所有整数数据类型,还使用了数据并行 SIMD/SSE 指令,请查看 HPCsharp nuget 包。 - DragonSpit
性能太差。 - nim

70

有的,使用.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);

2
为什么会有这样复杂的3.5版本之前的代码?foreach循环在所有C#版本中都可用。 - Jørn Schou-Rode
2
@Jørn:OP要求更简洁的方法。使用foreach只是将一行代码替换为另一行,而不是更短。除此之外,foreach完全可以使用,而且更易读。 - Ahmad Mageed
2
明白了。然而,与您的示例相比,以下代码可以节省18个字符:foreach (int i in arr) sum += i; - Jørn Schou-Rode

23

使用LINQ:

arr.Sum()

6
另一个选择是使用Aggregate()扩展方法。
var sum = arr.Aggregate((temp, x) => temp+x);

2
这似乎可以解决Sum无法解决的问题。由于某些原因,它不能在uint数组上工作,但Aggregate可以。 - John Ernest

5

这取决于你如何定义“更好”。如果你想让代码看起来更整洁,你可以像其他答案中提到的那样使用.Sum()方法。如果你想要操作运行得更快且有一个大数组,你可以将其分成子和再求和以实现并行化。


+1 对于性能改进的观点非常好,但老实说,我的初衷是想摆脱迭代。 - Filburt
(没人告诉Fil他刚刚将迭代推到了堆栈的几个级别下面) - user1228
@Will:老兄,如果我不写代码,你别指望我相信会有魔法发生;-) - Filburt
3
我猜在这样的并行优化变得有意义之前,数组可能需要非常大。 - Ian Mercer
是啊,自从什么时候for循环成为了不良实践? - Ed S.

4

对于非常大的数组,使用多台处理器/核心来执行计算可能会更加划算。

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);
});

3

上面的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);

由于正结果太大,超出了int数据类型的范围并溢出为负值,因此总和为-2147483648。

对于相同的输入数组,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包支持所有无符号数据类型。 - DragonSpit
一位贡献者提出了一个很好的想法,即 long sum = arr.Sum(x => (long)x);,它在使用Linq时在C#中非常有效。它为所有有符号整数数据类型(sbyte、short和int)提供了完全准确的求和,并避免了抛出溢出异常,代码也非常简洁。虽然性能不如上面的for循环高,但并非所有情况都需要高性能。 - DragonSpit

2
如果您不喜欢LINQ,最好使用foreach循环避免索引超出范围。
int[] arr = new int[] { 1, 2, 3 };
int sum = 0;
foreach (var item in arr)
{
   sum += item;
}

0
在我的一个应用程序中,我使用了以下代码:
public class ClassBlock
{
    public int[] p;
    public int Sum
    {
        get { int s = 0;  Array.ForEach(p, delegate (int i) { s += i; }); return s; }
    }
}

这与使用.Aggregate()扩展方法相同。 - John Alexiou

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接