วิธีการสรุปอาร์เรย์ของจำนวนเต็มใน C #


108

มีวิธีที่สั้นกว่าที่ดีกว่าการวนซ้ำบนอาร์เรย์หรือไม่?

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

ชี้แจง:

รหัสหลักที่ดีกว่าหมายถึงรหัสที่สะอาดกว่า แต่ยินดีต้อนรับคำแนะนำในการปรับปรุงประสิทธิภาพ (เช่นเดียวกับที่กล่าวไปแล้ว: การแยกอาร์เรย์ขนาดใหญ่)


มันไม่เหมือนกับว่าฉันกำลังมองหาการปรับปรุงประสิทธิภาพของนักฆ่า - ฉันแค่สงสัยว่าน้ำตาลวากยสัมพันธ์แบบนี้ไม่มีอยู่แล้ว: "มี String เข้าร่วม - ห่าอะไรเกี่ยวกับ int []?"


2
ดีขึ้นในทางใด? เร็วขึ้น? เขียนโค้ดน้อยลง?
Fredrik Mörk

คำตอบ:


186

โดยมีเงื่อนไขว่าคุณสามารถใช้. NET 3.5 (หรือใหม่กว่า) และ LINQ ได้ลอง

int sum = arr.Sum();

10
แลมด้าเอกลักษณ์ไม่จำเป็น ยกเว้นจะสร้างความสับสนให้กับคนใหม่ในทีม

12
เป็นที่น่าสังเกตว่าสิ่งนี้จะทำให้เกิดข้อผิดพลาดขึ้นSystem.OverflowExceptionหากผลลัพธ์จะมากกว่าที่คุณสามารถใส่ลงในจำนวนเต็ม 32 บิตที่มีลายเซ็น (เช่น (2 ^ 31) -1 หรือในภาษาอังกฤษ ~ 2.1 พันล้าน)
ChrisProsser

2
int sum = arr.AsParallel().Sum();เวอร์ชันที่เร็วกว่าซึ่งใช้หลายคอร์ของ CPU เพื่อหลีกเลี่ยงSystem.OverflowExceptionคุณสามารถใช้long sum = arr.AsParallel().Sum(x => (long)x);สำหรับเวอร์ชันที่เร็วกว่าที่หลีกเลี่ยงข้อยกเว้นล้นและรองรับประเภทข้อมูลจำนวนเต็มทั้งหมดและใช้คำแนะนำ SIMD / SSE แบบขนานข้อมูลโปรดดูที่แพ็คเกจ
HPCsharp nuget

66

ใช่มี. ด้วย. NET 3.5:

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

หากคุณไม่ได้ใช้. NET 3.5 คุณสามารถทำได้:

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

2
ทำไมต้องเป็นรุ่นก่อน 3.5 ที่ซับซ้อนเช่นนี้ foreachห่วงสามารถใช้ได้ในทุกรุ่นของ C #
Jørn Schou-Rode

2
@ Jørn: OP ขอวิธีที่สั้นกว่านี้ foreachเพียงทดแทนหนึ่งบรรทัดของรหัสอีกและไม่ได้เป็นที่สั้นกว่า นอกจากนั้น a foreachนั้นดีอย่างสมบูรณ์แบบและอ่านได้ง่ายกว่า
Ahmad Mageed

2
จุดที่ถ่าย อย่างไรก็ตามต่อไปนี้ประหยัด 18 ตัวอักษรเมื่อเทียบกับตัวอย่างของคุณ:foreach (int i in arr) sum += i;
Jørn Schou-Rode


5

ขึ้นอยู่กับว่าคุณกำหนดอย่างไรดีกว่า หากคุณต้องการให้โค้ดดูสะอาดขึ้นคุณสามารถใช้. sum () ตามที่กล่าวไว้ในคำตอบอื่น ๆ หากคุณต้องการให้การดำเนินการทำงานอย่างรวดเร็วและคุณมีอาร์เรย์ขนาดใหญ่คุณสามารถทำให้การดำเนินการขนานกันได้โดยการแบ่งออกเป็นผลรวมย่อยแล้วรวมผลลัพธ์


+1 จุดที่ดีมากในการปรับปรุงประสิทธิภาพ แต่จริงๆแล้วความปรารถนาแรกของฉันคือการกำจัดการทำซ้ำ
Filburt

(ไม่มีใครบอก Fil ว่าเขาดันการวนซ้ำสองสามระดับลงในสแต็ก)

@ Will: Dude - อย่าหวังว่าฉันจะเชื่อถ้าฉันไม่เขียนรหัสมายากลจะเกิดขึ้น ;-)
Filburt

3
ฉันสงสัยว่ามันจะต้องเป็นอาร์เรย์ขนาดใหญ่มากก่อนที่การเพิ่มประสิทธิภาพแบบขนานจะสมเหตุสมผล
Ian Mercer

ใช่แล้ว for loop กลายเป็นการฝึกที่ไม่ดีตั้งแต่เมื่อไหร่?
Ed S.

3

อีกทางเลือกหนึ่งคือใช้Aggregate()วิธีการขยาย

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

1
ดูเหมือนว่าจะใช้งานได้โดยที่ Sum ไม่ทำงาน มันจะไม่ทำงานกับอาร์เรย์ของ uint ด้วยเหตุผลบางประการ แต่ Aggregate จะ
John Ernest

2

หากคุณไม่ชอบ LINQ ควรใช้ foreach loop เพื่อหลีกเลี่ยงดัชนี

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

2

สำหรับอาร์เรย์ที่มีขนาดใหญ่มากอาจต้องเสียค่าใช้จ่ายในการคำนวณโดยใช้โปรเซสเซอร์ / คอร์ของเครื่องมากกว่าหนึ่งตัว

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

ปัญหาหนึ่งของการแก้ปัญหาสำหรับลูปข้างต้นคือสำหรับอาร์เรย์อินพุตต่อไปนี้ที่มีค่าบวกทั้งหมดผลรวมจะเป็นลบ:

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

ผลรวมคือ -2147483648 เนื่องจากผลลัพธ์ที่เป็นบวกนั้นใหญ่เกินไปสำหรับชนิดข้อมูล int และล้นเป็นค่าลบ

สำหรับอาร์เรย์อินพุตเดียวกันคำแนะนำ arr.Sum () ทำให้เกิดข้อยกเว้นล้น

วิธีแก้ปัญหาที่มีประสิทธิภาพมากขึ้นคือการใช้ประเภทข้อมูลที่ใหญ่กว่าเช่น "long" ในกรณีนี้สำหรับ "sum" ดังนี้:

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

การปรับปรุงเดียวกันนี้ใช้ได้กับการสรุปข้อมูลประเภทจำนวนเต็มอื่น ๆ เช่น short และ sbyte สำหรับอาร์เรย์ของชนิดข้อมูลจำนวนเต็มที่ไม่ได้ลงชื่อเช่น uint, ushort และ byte โดยใช้ long ที่ไม่ได้ลงชื่อ (ulong) สำหรับผลรวมจะหลีกเลี่ยงข้อยกเว้นล้น

โซลูชันสำหรับลูปยังเร็วกว่า Linq .um () หลายเท่า

เพื่อให้ทำงานได้เร็วยิ่งขึ้นแพ็คเกจ HPCsharp nuget จะใช้งานทั้งหมดนี้เวอร์ชัน sum () เช่นเดียวกับเวอร์ชัน SIMD / SSE และแบบขนานแบบมัลติคอร์เพื่อประสิทธิภาพที่เร็วขึ้นหลายเท่า


ความคิดที่ดี. และสำหรับอาร์เรย์จำนวนเต็มที่ไม่ได้ลงชื่อจะเป็นการดีที่จะสามารถทำ ulong sum = arr.Sum (x => (ulong) x); แต่น่าเศร้าที่ Linq.Sum () ไม่รองรับชนิดข้อมูลจำนวนเต็มที่ไม่ได้ลงชื่อ หากต้องการการสรุปแบบไม่ได้ลงชื่อแพ็คเกจ HPCsharp nuget จะรองรับสำหรับประเภทข้อมูลที่ไม่ได้ลงชื่อทั้งหมด
DragonSpit

ผู้ร่วมให้ข้อมูลได้ถอนแนวคิดที่ดีlong sum = arr.Sum(x => (long)x);ซึ่งทำงานได้ดีใน C # โดยใช้ Linq ให้ความแม่นยำเต็มรูปแบบสำหรับการสรุปสำหรับชนิดข้อมูลจำนวนเต็มที่ลงนามทั้งหมด: sbyte, short และ int นอกจากนี้ยังหลีกเลี่ยงการทิ้งข้อยกเว้นล้นและมีขนาดกะทัดรัด มันไม่ได้มีประสิทธิภาพสูงเท่าสำหรับลูปด้านบน แต่ประสิทธิภาพไม่จำเป็นในทุกกรณี
DragonSpit

0

การใช้ foreach จะเป็นโค้ดที่สั้นกว่า แต่อาจทำตามขั้นตอนเดียวกันกับรันไทม์หลังจากการปรับให้เหมาะสม JIT รับรู้การเปรียบเทียบกับความยาวในนิพจน์การควบคุม for-loop


0

ในแอปหนึ่งของฉันฉันใช้:

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

เช่นเดียวกับการใช้.Aggregate()วิธีการขยาย
John Alexiou

-1

การปรับปรุง Parallel แบบหลายคอร์ที่ดีของ 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;
    }

ซึ่งใช้ได้กับชนิดข้อมูลจำนวนเต็มที่ไม่ได้ลงชื่อเนื่องจาก C # รองรับเฉพาะ Interlocked เท่านั้น Add () สำหรับ int และ long การใช้งานข้างต้นยังสามารถแก้ไขได้อย่างง่ายดายเพื่อรองรับชนิดข้อมูลจำนวนเต็มและทศนิยมอื่น ๆ เพื่อทำการสรุปแบบขนานโดยใช้หลายคอร์ของ CPU ใช้ในแพ็คเกจ HPCsharp nuget


-7

ลองใช้รหัสนี้:

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

ผลลัพธ์คือ:

23


รหัสของคุณไม่ถูกต้องคุณต้องเปลี่ยน Console.WriteLine (ผลรวม); ด้วยผลรวมผลตอบแทน และมันจะทำงาน
Abdessamad Jadid
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.