Come riassumere una matrice di numeri interi in C #


108

Esiste un modo più breve migliore dell'iterazione sull'array?

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

una precisazione:

Primaria migliore significa codice più pulito, ma sono ben accetti anche suggerimenti sul miglioramento delle prestazioni. (Come già accennato: divisione di grandi array).


Non è che stavo cercando un miglioramento delle prestazioni killer - Mi chiedevo solo se questo tipo di zucchero sintattico non fosse già disponibile: "C'è String.Join - che diamine int []?".


2
Meglio in che modo? Più veloce? Meno codice scritto?
Fredrik Mörk

Risposte:


186

A condizione che sia possibile utilizzare .NET 3.5 (o più recente) e LINQ, provare

int sum = arr.Sum();

10
L'identità lambda non è necessaria. Tranne per confondere il nuovo ragazzo della squadra.

12
Vale la pena notare che questo genererà un errore System.OverflowExceptionse il risultato sarà maggiore di quello che puoi inserire in un intero con segno a 32 bit (ad esempio (2 ^ 31) -1 o in inglese ~ 2,1 miliardi).
ChrisProsser

2
int sum = arr.AsParallel().Sum();una versione più veloce che utilizza più core della CPU. Per evitare che System.OverflowExceptiontu possa utilizzare long sum = arr.AsParallel().Sum(x => (long)x);Per versioni ancora più veloci che evitano eccezioni di overflow e supportano tutti i tipi di dati interi e utilizzano istruzioni SIMD / SSE parallele ai dati, dai un'occhiata al pacchetto HPCsharp nuget
DragonSpit

66

Si C'è. Con .NET 3.5:

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

Se non stai usando .NET 3.5 puoi farlo:

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

2
Perché una versione pre 3.5 così contorta? Il foreachciclo è disponibile in tutte le versioni di C #.
Jørn Schou-Rode

2
@ Jørn: il PO ha chiesto un approccio più breve. A foreachsostituisce solo una riga di codice con un'altra e non è più breve. A parte questo, a foreachva benissimo ed è più leggibile.
Ahmad Mageed

2
Punto preso. Tuttavia, quanto segue consente di risparmiare 18 caratteri rispetto al campione:foreach (int i in arr) sum += i;
Jørn Schou-Rode


5

Dipende da come definisci meglio. Se vuoi che il codice appaia più pulito, puoi usare .Sum () come menzionato in altre risposte. Se si desidera che l'operazione venga eseguita rapidamente e si dispone di un ampio array, è possibile renderlo parallelo suddividendolo in somme secondarie e quindi sommare i risultati.


+1 Ottimo punto sul miglioramento delle prestazioni, ma onestamente il mio desiderio iniziale era di sbarazzarmi dell'iterazione.
Filburt

(nessuno dice a Fil che ha appena spinto l'iterazione di un paio di livelli verso il basso nello stack)

@ Will: Amico, non aspettarti che creda che se non scrivo il codice, la magia avverrà ;-)
Filburt

3
Sospetto che dovrebbe essere un array MOLTO grande prima che un'ottimizzazione parallela di questo tipo abbia senso.
Ian Mercer

Già, da quando un ciclo for è diventato una cattiva pratica?
Ed S.

3

Un'alternativa anche per usare il Aggregate()metodo dell'estensione.

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

1
Questo sembra funzionare dove Sum no. Non funzionerebbe su un array di uint per qualche motivo, ma Aggregate sì.
John Ernest

2

Se non si preferisce LINQ, è preferibile utilizzare il ciclo foreach per evitare di uscire dall'indice.

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

2

Per array estremamente grandi può essere necessario eseguire il calcolo utilizzando più di un processore / core della macchina.

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

Un problema con le soluzioni del ciclo for sopra è che per il seguente array di input con tutti i valori positivi, il risultato della somma è negativo:

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

La somma è -2147483648, poiché il risultato positivo è troppo grande per il tipo di dati int e supera un valore negativo.

Per lo stesso array di input, i suggerimenti arr.Sum () causano la generazione di un'eccezione di overflow.

Una soluzione più robusta consiste nell'utilizzare un tipo di dati più grande, ad esempio "lungo" in questo caso, per la "somma" come segue:

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

Lo stesso miglioramento funziona per la somma di altri tipi di dati interi, come short e sbyte. Per array di tipi di dati interi senza segno come uint, ushort e byte, l'utilizzo di un lungo senza segno (ulong) per la somma evita l'eccezione di overflow.

La soluzione del ciclo for è anche molte volte più veloce di Linq .Sum ()

Per funzionare ancora più velocemente, il pacchetto HPCsharp nuget implementa tutte queste versioni .Sum () così come le versioni SIMD / SSE e parallele multi-core, per prestazioni molte volte più veloci.


Grande idea. E, per array di interi senza segno, sarebbe bello poter eseguire ulong sum = arr.Sum (x => (ulong) x); Ma, purtroppo, Linq .Sum () non supporta i tipi di dati interi senza segno. Se è necessaria una somma non firmata, il pacchetto nuget HPCsharp la supporta per tutti i tipi di dati non firmati.
DragonSpit

Un collaboratore ha ritirato una bella idea long sum = arr.Sum(x => (long)x);che funziona bene in C # usando Linq. Fornisce la massima precisione per la somma di tutti i tipi di dati interi con segno: sbyte, short e int. Evita anche di generare un'eccezione di overflow ed è piacevolmente compatto. Non è così performante come il ciclo for sopra, ma le prestazioni non sono necessarie in tutti i casi.
DragonSpit

0

L'utilizzo di foreach sarebbe un codice più breve, ma probabilmente eseguire esattamente gli stessi passaggi in fase di esecuzione dopo che l'ottimizzazione JIT ha riconosciuto il confronto con Length nell'espressione di controllo del ciclo for.


0

In una delle mie app ho usato:

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

Equivale a usare il .Aggregate()metodo di estensione.
John Alexiou,

-1

Un miglioramento della simpatica implementazione parallela multi-core di Theodor Zoulias.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;
    }

che funziona per i tipi di dati interi senza segno, poiché C # supporta solo Interlocked.Add () per int e long. L'implementazione di cui sopra può anche essere facilmente modificata per supportare altri tipi di dati interi e in virgola mobile per eseguire la somma in parallelo utilizzando più core della CPU. Viene utilizzato nel pacchetto nuget HPCsharp.


-7

Prova questo codice:

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

Il risultato è:

23


Il tuo codice non è corretto, devi sostituire Console.WriteLine (sum); con somma di ritorno; e funzionerà
Abdessamad Jadid
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.