Jak podsumować tablicę liczb całkowitych w C #

108

Czy istnieje lepszy krótszy sposób niż iterowanie po tablicy?

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

wyjaśnienie:

Lepszy podstawowy oznacza czystszy kod, ale wskazówki dotyczące poprawy wydajności są również mile widziane. (Jak już wspomniano: dzielenie dużych tablic).


To nie tak, że szukałem zabójczej poprawy wydajności - po prostu zastanawiałem się, czy ten rodzaj cukru syntaktycznego nie był już dostępny: „Jest String.Join - co do cholery z int []?”.

Filburt
źródło
2
Lepiej w jaki sposób? Szybciej? Mniej napisany kod?
Fredrik Mörk

Odpowiedzi:

186

Pod warunkiem, że możesz używać .NET 3.5 (lub nowszego) i LINQ, wypróbuj

int sum = arr.Sum();
Tomas Vana
źródło
10
Lambda tożsamości nie jest konieczna. Oprócz zmylenia nowego gościa w zespole.
12
Warto zauważyć, że spowoduje to wystąpienie błędu, System.OverflowExceptionjeśli wynik będzie większy niż można zmieścić w 32-bitowej liczbie całkowitej ze znakiem (tj. (2 ^ 31) -1 lub po angielsku ~ 2,1 miliarda).
ChrisProsser
2
int sum = arr.AsParallel().Sum();szybsza wersja, która wykorzystuje wiele rdzeni procesora. Aby tego uniknąć System.OverflowException, możesz użyć long sum = arr.AsParallel().Sum(x => (long)x);Dla jeszcze szybszych wersji, które unikają wyjątku przepełnienia i obsługują wszystkie typy danych całkowitoliczbowych i używają równoległych instrukcji SIMD / SSE danych, spójrz na pakiet nuget HPCsharp
DragonSpit
66

Tak jest. Z .NET 3.5:

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

Jeśli nie używasz .NET 3.5, możesz to zrobić:

int sum = 0;
Array.ForEach(arr, delegate(int i) { sum += i; });
Console.WriteLine(sum);
Ahmad Mageed
źródło
2
Skąd taka zawiła wersja przed 3.5? foreachPętla jest dostępny we wszystkich wersjach C #.
Jørn Schou-Rode
2
@ Jørn: PO poprosił o krótsze podejście. A foreachpo prostu zastępuje jeden wiersz kodu innym i nie jest krótszy. Poza tym a foreachjest w porządku i jest bardziej czytelny.
Ahmad Mageed
2
Punkt zajęty. Jednak poniższe zapisuje 18 znaków w porównaniu z twoją próbką:foreach (int i in arr) sum += i;
Jørn Schou-Rode
5

To zależy od tego, jak lepiej zdefiniujesz. Jeśli chcesz, aby kod wyglądał bardziej czytelnie, możesz użyć .Sum (), jak wspomniano w innych odpowiedziach. Jeśli chcesz, aby operacja przebiegała szybko i masz dużą tablicę, możesz ją wykonać równolegle, dzieląc ją na sumy częściowe, a następnie zsumuj wyniki.

unholysampler
źródło
+1 Bardzo dobry punkt na poprawę wydajności, ale szczerze mówiąc, moim początkowym życzeniem było pozbycie się iteracji.
Filburt
(nikt nie mówi Filowi, że właśnie przesunął iterację o kilka poziomów w dół)
@Will: Stary - nie oczekuj, że uwierzę, jeśli nie napiszę kodu, magia się wydarzy ;-)
Filburt
3
Podejrzewam, że musiałaby to być BARDZO duża tablica, zanim taka równoległa optymalizacja miałaby sens.
Ian Mercer
Tak, od kiedy pętla for stała się złą praktyką?
Ed S.
3

Alternatywą jest również użycie Aggregate()metody rozszerzenia.

var sum = arr.Aggregate((temp, x) => temp+x);
John Alexiou
źródło
1
Wydaje się, że działa to tam, gdzie Sum nie działa. Z jakiegoś powodu nie działałby na tablicy uint, ale Aggregate tak.
John Ernest,
2

Jeśli nie wolisz LINQ, lepiej jest użyć pętli foreach, aby uniknąć braku indeksu.

int[] arr = new int[] { 1, 2, 3 };
int sum = 0;
foreach (var item in arr)
{
   sum += item;
}
HENG Vongkol
źródło
2

W przypadku bardzo dużych tablic może się opłacić wykonanie obliczeń przy użyciu więcej niż jednego procesora / rdzeni maszyny.

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);
});
Theodor Zoulias
źródło
2

Jednym z problemów z powyższymi rozwiązaniami pętli for jest to, że dla następującej tablicy wejściowej ze wszystkimi dodatnimi wartościami suma wyników jest ujemna:

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

Suma wynosi -2147483648, ponieważ wynik dodatni jest zbyt duży dla typu danych int i powoduje przepełnienie do wartości ujemnej.

Dla tej samej tablicy wejściowej sugestie arr.Sum () powodują zgłoszenie wyjątku przepełnienia.

Bardziej niezawodnym rozwiązaniem jest użycie większego typu danych, takiego jak „long” w tym przypadku, dla „sumy” w następujący sposób:

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

To samo ulepszenie działa w przypadku sumowania innych typów danych całkowitych, takich jak short i sbyte. W przypadku tablic liczb całkowitych bez znaku, takich jak uint, ushort i bajt, użycie długości bez znaku (ulong) dla sumy pozwala uniknąć wyjątku przepełnienia.

Rozwiązanie pętli for jest również wielokrotnie szybsze niż Linq .Sum ()

Aby działać jeszcze szybciej, pakiet nuget HPCsharp implementuje wszystkie te wersje .Sum (), a także wersje SIMD / SSE i wielordzeniowe wersje równoległe, co zapewnia wielokrotnie wyższą wydajność.

DragonSpit
źródło
Świetny pomysł. A dla tablicy liczb całkowitych bez znaku byłoby miło móc wykonać ulong sum = arr.Sum (x => (ulong) x); Niestety, Linq .Sum () nie obsługuje liczb całkowitych bez znaku. Jeśli sumowanie bez znaku jest potrzebne, pakiet nuget HPCsharp obsługuje go dla wszystkich typów danych bez znaku.
DragonSpit
Współtwórca wycofał fajny pomysł, long sum = arr.Sum(x => (long)x);który dobrze działa w C # przy użyciu Linq. Zapewnia pełną dokładność sumowania wszystkich typów danych całkowitych ze znakiem: sbyte, short i int. Unika również zgłaszania wyjątku przepełnienia i jest ładnie zwarty. Nie jest tak wydajna, jak powyższa pętla for, ale nie we wszystkich przypadkach jest potrzebna.
DragonSpit
0

Użycie foreach byłoby krótszym kodem, ale prawdopodobnie wykonaj dokładnie te same kroki w czasie wykonywania po tym, jak optymalizacja JIT rozpozna porównanie z długością w wyrażeniu sterującym pętli for.

Ben Voigt
źródło
0

W jednej z moich aplikacji użyłem:

public class ClassBlock
{
    public int[] p;
    public int Sum
    {
        get { int s = 0;  Array.ForEach(p, delegate (int i) { s += i; }); return s; }
    }
}
merrais
źródło
To jest to samo, co użycie .Aggregate()metody rozszerzenia.
John Alexiou
-1

Ulepszenie ładnej wielordzeniowej implementacji równoległej Theodora Zouliasa.

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

który działa dla typów danych całkowitych bez znaku, ponieważ C # obsługuje tylko Interlocked.Add () dla int i long. Powyższą implementację można również łatwo zmodyfikować, aby obsługiwała inne typy danych całkowitoliczbowych i zmiennoprzecinkowych, aby wykonywać sumowanie równolegle przy użyciu wielu rdzeni procesora. Jest używany w pakiecie nuget HPCsharp.

DragonSpit
źródło
-7

Wypróbuj ten kod:

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

Wynik to:

23

Ibne Nahian
źródło
Twój kod nie jest poprawny, musisz wymienić Console.WriteLine (suma); z sumą zwrotu; i to zadziała
Abdessamad Jadid