Cách tính tổng một mảng số nguyên trong C #


108

Có cách nào ngắn hơn tốt hơn là lặp qua mảng không?

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

làm rõ:

Chính tốt hơn có nghĩa là mã sạch hơn nhưng các gợi ý về cải thiện hiệu suất cũng được hoan nghênh. (Giống như đã đề cập: tách mảng lớn).


Nó không giống như tôi đang tìm kiếm sự cải thiện hiệu suất của kẻ giết người - tôi chỉ tự hỏi liệu loại đường cú pháp này chưa có sẵn: "Có String.Join - cái quái gì về int []?".


2
Tốt hơn theo cách nào? Nhanh hơn? Mã ít được viết hơn?
Fredrik Mörk

Câu trả lời:


186

Với điều kiện bạn có thể sử dụng .NET 3.5 (hoặc mới hơn) và LINQ, hãy thử

int sum = arr.Sum();

10
Lambda nhận dạng không cần thiết. Ngoại trừ việc nhầm lẫn với anh chàng mới trong đội.

12
Cần lưu ý rằng điều này sẽ gây ra lỗi System.OverflowExceptionnếu kết quả lớn hơn bạn có thể điền vào một số nguyên 32 bit có dấu (tức là (2 ^ 31) -1 hoặc trong tiếng Anh ~ 2,1 tỷ).
ChrisProsser

2
int sum = arr.AsParallel().Sum();phiên bản nhanh hơn sử dụng nhiều lõi của CPU. Để tránh System.OverflowExceptionbạn có thể sử dụng long sum = arr.AsParallel().Sum(x => (long)x);Ví thậm chí nhanh hơn phiên bản mà tránh tràn ngoại lệ và hỗ trợ tất cả nguyên liệu loại và sử dụng dữ liệu song song các chỉ lệnh SIMD / SSE, hãy nhìn vào gói NuGet HPCsharp
DragonSpit

66

Có, có. Với .NET 3.5:

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

Nếu bạn không sử dụng .NET 3.5, bạn có thể thực hiện việc này:

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

2
Tại sao một phiên bản trước 3.5 phức tạp như vậy? Các foreachvòng lặp có sẵn trong tất cả các phiên bản của C #.
Jørn Schou-Rode

2
@ Jørn: OP đã yêu cầu một cách tiếp cận ngắn hơn. A foreachchỉ thay thế một dòng mã cho một dòng mã khác và không ngắn hơn. Ngoài ra, a foreachhoàn toàn ổn và dễ đọc hơn.
Ahmad Mageed vào

2
Đã thực hiện. Tuy nhiên, sau đây tiết kiệm 18 ký tự so với mẫu của bạn:foreach (int i in arr) sum += i;
Jørn SCHOU-Rode


5

Nó phụ thuộc vào cách bạn xác định tốt hơn. Nếu bạn muốn mã trông sạch hơn, bạn có thể sử dụng .Sum () như đã đề cập trong các câu trả lời khác. Nếu bạn muốn hoạt động chạy nhanh và bạn có một mảng lớn, bạn có thể thực hiện song song nó bằng cách chia nó thành các tổng phụ rồi tính tổng kết quả.


+1 Điểm rất tốt về cải thiện hiệu suất nhưng thành thật mà nói mong muốn ban đầu của tôi là loại bỏ sự lặp lại.
Filburt

(không ai nói với Fil rằng anh ấy vừa đẩy lặp lại một vài cấp độ xuống ngăn xếp)

@Will: Anh bạn - đừng mong đợi tôi tin nếu tôi không viết mã, điều kỳ diệu sẽ xảy ra ;-)
Filburt

3
Tôi nghi ngờ rằng nó sẽ phải là một mảng RẤT lớn trước khi tối ưu hóa song song như vậy sẽ có ý nghĩa.
Ian Mercer

Yeah, từ khi nào vòng lặp for trở thành một bài tập xấu?
Ed S.

3

Một thay thế cũng là nó để sử dụng Aggregate()phương pháp mở rộng.

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

1
Điều này dường như hiệu quả khi Sum không. Nó sẽ không hoạt động trên một mảng uint vì một số lý do nhưng Aggregate thì có.
John Ernest

2

Nếu bạn không thích LINQ, tốt hơn là sử dụng vòng lặp foreach để tránh bị out index.

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

2

Đối với các mảng cực lớn, nó có thể phải trả tiền để thực hiện phép tính bằng cách sử dụng nhiều bộ xử lý / lõi của máy.

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

2

Một vấn đề với các giải pháp vòng lặp for ở trên là đối với mảng đầu vào sau với tất cả các giá trị dương, kết quả tổng là âm:

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

Tổng là -2147483648, vì kết quả dương quá lớn đối với kiểu dữ liệu int và tràn thành giá trị âm.

Đối với cùng một mảng đầu vào, các đề xuất arr.Sum () gây ra một ngoại lệ tràn được ném ra.

Một giải pháp mạnh mẽ hơn là sử dụng kiểu dữ liệu lớn hơn, chẳng hạn như "dài" trong trường hợp này, cho "tổng" như sau:

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

Cải tiến tương tự cũng hoạt động đối với tính tổng của các kiểu dữ liệu số nguyên khác, chẳng hạn như short và sbyte. Đối với các mảng kiểu dữ liệu số nguyên không dấu như uint, ushort và byte, việc sử dụng long không dấu (ulong) cho tổng sẽ tránh được ngoại lệ tràn.

Giải pháp vòng lặp for cũng nhanh hơn nhiều lần so với Linq .Sum ()

Để chạy nhanh hơn nữa, gói HPCsharp nuget triển khai tất cả các phiên bản .Sum () này cũng như các phiên bản SIMD / SSE và các phiên bản song song đa lõi, cho hiệu suất nhanh hơn nhiều lần.


Ý tưởng tuyệt vời. Và, đối với mảng số nguyên không dấu, sẽ rất tuyệt nếu có thể thực hiện ulong sum = arr.Sum (x => (ulong) x); Nhưng thật đáng buồn, Linq .Sum () không hỗ trợ các kiểu dữ liệu số nguyên không dấu. Nếu cần tổng hợp chưa ký, gói nuget HPCsharp hỗ trợ nó cho tất cả các kiểu dữ liệu chưa ký.
DragonSpit

Một người đóng góp đã rút lại một ý tưởng hay về cách long sum = arr.Sum(x => (long)x);hoạt động tốt trong C # bằng cách sử dụng Linq. Nó cung cấp độ chính xác đầy đủ cho phép tính tổng cho tất cả các kiểu dữ liệu số nguyên có dấu: sbyte, short và int. Nó cũng tránh tạo ra một ngoại lệ tràn và khá nhỏ gọn. Nó không có hiệu suất cao như vòng lặp for ở trên, nhưng hiệu suất không cần thiết trong mọi trường hợp.
DragonSpit

0

Sử dụng foreach sẽ là mã ngắn hơn, nhưng có thể thực hiện chính xác các bước tương tự trong thời gian chạy sau khi tối ưu hóa JIT nhận ra so sánh với Độ dài trong biểu thức điều khiển vòng lặp.


0

Trong một trong những ứng dụng của mình, tôi đã sử dụng:

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

Điều này cũng giống như sử dụng .Aggregate()phương pháp mở rộng.
John Alexiou

-1

Một cải tiến về Parallel đa lõi tốt đẹp của Theodor Zoulias.

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

hoạt động cho các kiểu dữ liệu số nguyên không dấu, vì C # chỉ hỗ trợ Interlocked.Add () cho int và long. Việc triển khai trên cũng có thể dễ dàng sửa đổi để hỗ trợ các kiểu dữ liệu số nguyên và dấu phẩy động khác để thực hiện phép tính tổng song song bằng cách sử dụng nhiều lõi của CPU. Nó được sử dụng trong gói nuget HPCsharp.


-7

Hãy thử mã này:

using System;

namespace Array
{
    class Program
    {
        static void Main()
        {
            int[] number = new int[] {5, 5, 6, 7};

            int sum = 0;
            for (int i = 0; i <number.Length; i++)
            {
                sum += number[i];
            }
            Console.WriteLine(sum);
        }
    }
} 

Kết quả là:

23


Mã của bạn không đúng, bạn phải thay thế Console.WriteLine (sum); với tổng trở lại; và nó sẽ hoạt động
Abdessamad Jadid
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.