C # 'da bir tamsayı dizisi nasıl toplanır


108

Dizi üzerinde yinelemekten daha kısa bir yol var mı ?

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

açıklama:

Daha iyi birincil, daha temiz kod anlamına gelir, ancak performans iyileştirme ipuçları da memnuniyetle karşılanır. (Daha önce bahsedildiği gibi: büyük dizileri bölmek).


Öldürücü performans iyileştirmesi aradığımdan değil - sadece bu tür bir sözdizimsel şekeri zaten mevcut olup olmadığını merak ettim : "String.Join var - int [] ne halt ediyor?".


2
Ne şekilde daha iyi? Daha hızlı? Daha az yazılmış kod mu?
Fredrik Mörk

Yanıtlar:


186

NET 3.5 (veya daha yenisi) ve LINQ kullanabilmeniz koşuluyla, deneyin

int sum = arr.Sum();

10
Lambda kimliği gerekli değildir. Takımdaki yeni adamın kafasını karıştırmak dışında.

12
Sonuç, System.OverflowExceptionişaretli bir 32 bit tam sayıya sığdırabileceğinizden daha büyükse (yani (2 ^ 31) -1 veya ingilizce ~ 2.1 milyar), bunun bir hata oluşturacağına dikkat etmek önemlidir.
ChrisProsser

2
int sum = arr.AsParallel().Sum();CPU'nun birden çok çekirdeğini kullanan daha hızlı bir sürüm. Kaçınmak System.OverflowExceptioniçin kullanabilirsiniz long sum = arr.AsParallel().Sum(x => (long)x);Taşma istisnasından kaçınan ve tüm tamsayı veri türlerini destekleyen ve paralel veri SIMD / SSE talimatlarını kullanan daha hızlı sürümler için, HPCsharp nuget paketine bir göz atın
DragonSpit

66

Evet var. .NET 3.5 ile:

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

.NET 3.5 kullanmıyorsanız, bunu yapabilirsiniz:

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

2
Neden bu kadar kıvrımlı bir 3.5 öncesi sürüm? foreachDöngü C # tüm sürümlerinde kullanılabilir.
Jørn Schou-Rode

2
@ Jørn: OP daha kısa bir yaklaşım istedi. Bir foreachbaşka ilişkin bir satır kodu yerine değil kısadır. Bunun dışında a foreachmükemmel derecede iyi ve daha okunaklı.
Ahmad Mageed

2
Alınan nokta. Yine de, aşağıdaki örnek örneğinize kıyasla 18 karakter tasarruf sağlıyor:foreach (int i in arr) sum += i;
Jørn Schou-Rode


5

Nasıl daha iyi tanımladığına bağlı. Kodun daha temiz görünmesini istiyorsanız, diğer yanıtlarda belirtildiği gibi .Sum () kullanabilirsiniz. İşlemin hızlı çalışmasını istiyorsanız ve büyük bir diziniz varsa, onu alt toplamlara bölerek paralel hale getirebilir ve ardından sonuçları toplayabilirsiniz.


+1 Performans iyileştirme konusunda çok iyi bir nokta ama dürüst olmak gerekirse ilk dileğim yinelemeden kurtulmaktı.
Filburt

(kimse

@Will: Dostum - kodu
yazmazsam

3
Böyle bir paralel optimizasyonun mantıklı olabilmesi için ÇOK büyük bir dizi olması gerektiğinden şüpheleniyorum.
Ian Mercer

Evet, ne zamandan beri for döngüsü kötü bir antrenmana dönüştü?
Ed S.

3

Bir alternatif de Aggregate()uzantı yöntemini kullanmaktır .

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

1
Bu, Sum'un çalışmadığı yerlerde işe yarıyor gibi görünüyor. Bir nedenden ötürü bir uint dizisi üzerinde çalışmaz, ancak Aggregate çalışır.
John Ernest

2

LINQ'yu tercih etmiyorsanız, indeks dışı kalmamak için foreach döngüsünü kullanmak daha iyidir.

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

2

Çok büyük diziler için, hesaplamayı makinenin birden fazla işlemcisi / çekirdeği kullanarak gerçekleştirmek faydalı olabilir.

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

Yukarıdaki for döngüsü çözümleriyle ilgili bir sorun, tüm pozitif değerlere sahip aşağıdaki giriş dizisi için toplam sonucun negatif olmasıdır:

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

Pozitif sonuç int veri türü için çok büyük olduğundan ve negatif bir değere taştığından, toplam -2147483648'dir.

Aynı girdi dizisi için arr.Sum () önerileri, bir taşma istisnasının atılmasına neden olur.

Daha sağlam bir çözüm, aşağıdaki gibi "toplam" için "uzun" gibi daha büyük bir veri türü kullanmaktır:

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

Aynı iyileştirme, kısa ve sbyte gibi diğer tamsayı veri türlerinin toplamı için de işe yarar. Uint, ushort ve bayt gibi işaretsiz tamsayı veri türlerinin dizileri için, toplam için işaretsiz uzun (ulong) kullanmak taşma istisnasını önler.

Ayrıca for döngüsü çözümü, Linq .Sum () 'dan birçok kez daha hızlıdır.

Daha da hızlı çalıştırmak için, HPCsharp nuget paketi, tüm bu .Sum () sürümlerinin yanı sıra SIMD / SSE sürümlerini ve çok çekirdekli paralel sürümleri birçok kez daha hızlı performans için uygular.


İyi fikir. Ve işaretsiz tamsayı dizisi için ulong sum = arr.Sum (x => (ulong) x); Ancak ne yazık ki Linq .Sum () işaretsiz tamsayı veri türlerini desteklemiyor. İmzasız toplama gerekiyorsa, HPCsharp nuget paketi bunu tüm imzasız veri türleri için destekler.
DragonSpit

Katkıda bulunanlardan biri long sum = arr.Sum(x => (long)x);Linq kullanarak C # ile güzelce çalışan güzel bir fikri geri çekti . Tüm işaretli tamsayı veri türleri için tam doğruluk sağlar: sbyte, short ve int. Aynı zamanda bir taşma istisnası atılmasını önler ve hoş bir şekilde kompakttır. Yukarıdaki for döngüsü kadar yüksek performans değil, ancak her durumda performans gerekli değildir.
DragonSpit

0

Foreach'i kullanmak daha kısa bir kod olurdu, ancak muhtemelen JIT optimizasyonu for-loop kontrol ifadesinde Length ile karşılaştırmayı tanıdıktan sonra çalışma zamanında tam olarak aynı adımları uygulayın.


0

Kullandığım uygulamalarımdan birinde:

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

Bu, .Aggregate()uzantı yöntemini kullanmakla aynıdır .
John Alexiou

-1

Theodor Zoulias'ın güzel çok çekirdekli Parallel.ForEach uygulamasında bir gelişme:

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

C # yalnızca int ve long için Interlocked.Add () 'yi desteklediğinden, işaretsiz tamsayı veri türleri için çalışır. Yukarıdaki uygulama, CPU'nun birden çok çekirdeğini kullanarak paralel olarak toplama yapmak için diğer tamsayı ve kayan noktalı veri türlerini desteklemek için kolayca değiştirilebilir. HPCsharp nuget paketinde kullanılır.


-7

Bu kodu deneyin:

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

Sonuç:

23


Kodunuz Doğru değil, Console.WriteLine (sum) 'u değiştirmeniz gerekir; getiri toplamı ile; ve işe yarayacak
Abdessamad Jadid
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.