Cómo sumar una matriz de números enteros en C #

108

¿Existe una forma mejor y más corta que iterar sobre la matriz?

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

aclaración:

Un código primario mejor significa un código más limpio, pero también se agradecen las sugerencias sobre la mejora del rendimiento. (Como ya se mencionó: dividir matrices grandes).


No es como si estuviera buscando una mejora espectacular en el rendimiento, solo me preguntaba si este tipo de azúcar sintáctico no estaba ya disponible: "Hay String.Join - ¿qué diablos pasa con int []?".

Filburt
fuente
2
¿Mejor de qué manera? ¿Más rápido? ¿Menos código escrito?
Fredrik Mörk

Respuestas:

186

Siempre que pueda usar .NET 3.5 (o más reciente) y LINQ, intente

int sum = arr.Sum();
Tomas Vana
fuente
10
La lambda de identidad no es necesaria. Excepto para confundir al nuevo chico del equipo.
12
Vale la pena señalar que esto generará un error System.OverflowExceptionsi el resultado fuera mayor de lo que puede caber en un entero de 32 bits con signo (es decir, (2 ^ 31) -1 o en inglés ~ 2.1 mil millones).
ChrisProsser
2
int sum = arr.AsParallel().Sum();una versión más rápida que usa múltiples núcleos de la CPU. Para evitar System.OverflowException, puede usar long sum = arr.AsParallel().Sum(x => (long)x);Para versiones aún más rápidas que evitan la excepción de desbordamiento y admiten todos los tipos de datos enteros y usan instrucciones SIMD / SSE paralelas de datos, eche un vistazo al paquete HPCsharp nuget
DragonSpit
66

Sí hay. Con .NET 3.5:

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

Si no está utilizando .NET 3.5, puede hacer esto:

int sum = 0;
Array.ForEach(arr, delegate(int i) { sum += i; });
Console.WriteLine(sum);
Ahmad Mageed
fuente
2
¿Por qué una versión pre 3.5 tan complicada? El foreachbucle está disponible en todas las versiones de C #.
Jørn Schou-Rode
2
@ Jørn: el PO pidió un enfoque más corto. A foreachsimplemente sustituye una línea de código por otra y no es más corta. Aparte de eso, a foreachestá perfectamente bien y es más legible.
Ahmad Mageed
2
Punto a favor. Sin embargo, lo siguiente guarda 18 caracteres en comparación con su muestra:foreach (int i in arr) sum += i;
Jørn Schou-Rode
20

Con LINQ:

arr.Sum()
Chris
fuente
5

Depende de cómo se defina mejor. Si desea que el código se vea más limpio, puede usar .Sum () como se menciona en otras respuestas. Si desea que la operación se ejecute rápidamente y tiene una matriz grande, puede hacerla paralela dividiéndola en sumas secundarias y luego sumando los resultados.

profano
fuente
+1 Muy buen punto en la mejora del rendimiento, pero sinceramente, mi deseo inicial era deshacerme de la iteración.
Filburt
(nadie diga Fil que simplemente empujó la iteración de un par de niveles abajo de la pila)
@Will: Amigo, no esperes que crea que si no escribo el código, la magia sucederá ;-)
Filburt
3
Sospecho que tendría que ser una matriz MUY grande antes de que tal optimización paralela tuviera sentido.
Ian Mercer
Sí, ¿desde cuándo un bucle for se convirtió en una mala práctica?
Ed S.
3

Una alternativa también es utilizar el Aggregate()método de extensión.

var sum = arr.Aggregate((temp, x) => temp+x);
John Alexiou
fuente
1
Esto parece funcionar donde Sum no lo hace. No funcionaría en una matriz de uint por alguna razón, pero Aggregate sí.
John Ernest
2

Si no prefiere LINQ, es mejor usar foreach loop para evitar el índice.

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

Para arreglos extremadamente grandes, puede resultar útil realizar el cálculo utilizando más de un procesador / núcleo de la máquina.

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
fuente
2

Un problema con las soluciones de bucle for anteriores es que para la siguiente matriz de entrada con todos los valores positivos, el resultado de la suma es 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 suma es -2147483648, ya que el resultado positivo es demasiado grande para el tipo de datos int y se desborda en un valor negativo.

Para la misma matriz de entrada, las sugerencias arr.Sum () provocan que se lance una excepción de desbordamiento.

Una solución más sólida es utilizar un tipo de datos más grande, como un "largo" en este caso, para la "suma" de la siguiente manera:

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

La misma mejora funciona para la suma de otros tipos de datos enteros, como short y sbyte. Para matrices de tipos de datos enteros sin signo como uint, ushort y byte, el uso de unsigned long (ulong) para la suma evita la excepción de desbordamiento.

La solución de bucle for también es muchas veces más rápida que Linq .Sum ()

Para ejecutarse aún más rápido, el paquete HPCsharp nuget implementa todas estas versiones .Sum (), así como las versiones SIMD / SSE y las paralelas multinúcleo, para un rendimiento mucho más rápido.

DragonSpit
fuente
Gran idea. Y, para una matriz de enteros sin signo, sería bueno poder hacer ulong sum = arr.Sum (x => (ulong) x); Pero, lamentablemente, Linq .Sum () no admite tipos de datos enteros sin firmar. Si se necesita una suma sin firmar, el paquete nuget de HPCsharp la admite para todos los tipos de datos sin firmar.
DragonSpit
Un colaborador retiró una buena idea de long sum = arr.Sum(x => (long)x);cuál funciona bien en C # usando Linq. Proporciona la máxima precisión para la suma de todos los tipos de datos enteros con signo: sbyte, short e int. También evita lanzar una excepción de desbordamiento y es muy compacto. No tiene un rendimiento tan alto como el bucle for anterior, pero el rendimiento no es necesario en todos los casos.
DragonSpit
0

El uso de foreach sería un código más corto, pero probablemente realice exactamente los mismos pasos en tiempo de ejecución después de que la optimización JIT reconozca la comparación con la longitud en la expresión de control del bucle for.

Ben Voigt
fuente
0

En una de mis aplicaciones usé:

public class ClassBlock
{
    public int[] p;
    public int Sum
    {
        get { int s = 0;  Array.ForEach(p, delegate (int i) { s += i; }); return s; }
    }
}
Merrais
fuente
Es lo mismo que usar el .Aggregate()método de extensión.
John Alexiou
-1

Una mejora en la buena implementación de Parallel.ForEach multinúcleo de 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;
    }

que funciona para tipos de datos enteros sin signo, ya que C # solo admite Interlocked.Add () para int y long. La implementación anterior también se puede modificar fácilmente para admitir otros tipos de datos enteros y de punto flotante para realizar la suma en paralelo utilizando múltiples núcleos de la CPU. Se utiliza en el paquete nuget de HPCsharp.

DragonSpit
fuente
-7

Prueba este código:

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

El resultado es:

23

Ibne Nahian
fuente
Su código no es correcto, debe reemplazar Console.WriteLine (sum); con suma de devolución; y funcionará
Abdessamad Jadid