c# خوارزميات - شرح LINQ Aggregate خوارزمية




الفرز الترتيب (10)

يعتمد جزئياً على الحمل الزائد الذي تتحدث عنه ، لكن الفكرة الأساسية هي:

  • ابدأ بالبذور على أنها "القيمة الحالية"
  • تكرار عبر التسلسل. لكل قيمة في التسلسل:
    • تطبيق وظيفة محددة من قبل المستخدم لتحويل (currentValue, sequenceValue) إلى (nextValue)
    • Set currentValue = nextValue
  • ارجع إلى currentValue النهائية currentValue

يمكنك العثور على المنشور Aggregate في سلسلة EdulinQ الخاصة بي - إنه يتضمن وصفًا أكثر تفصيلاً (بما في ذلك الأحمال الزائدة) وعمليات التنفيذ.

مثال بسيط واحد هو استخدام Aggregate كبديل Count :

// 0 is the seed, and for each item, we effectively increment the current value.
// In this case we can ignore "item" itself.
int count = sequence.Aggregate(0, (current, item) => current + 1);

أو ربما جمع كل أطوال السلاسل في سلسلة من الأوتار:

int total = sequence.Aggregate(0, (current, item) => current + item.Length);

أنا شخصياً نادراً ما أجد Aggregate مفيدة - طرق التجميع "المصممة" عادة ما تكون جيدة بما يكفي بالنسبة لي.

قد يبدو هذا أعرجًا ، لكنني لم أتمكن من العثور على تفسير جيد Aggregate .

جيد يعني قصير وصفي وشامل مع مثال صغير وواضح.


بالإضافة إلى جميع الإجابات الرائعة هنا بالفعل ، لقد استخدمتها أيضًا في السير على عنصر من خلال سلسلة من خطوات التحويل.

إذا تم تنفيذ تحويل كـ Func<T,T> ، يمكنك إضافة العديد من التحويلات إلى List<Func<T,T>> واستخدام Aggregate للمشي على سبيل المثال T خلال كل خطوة.

مثال أكثر واقعية

أنت تريد أن تأخذ قيمة string ، وتمشيها من خلال سلسلة من التحويلات النصية التي يمكن بناؤها برمجيًا.

var transformationPipeLine = new List<Func<string, string>>();
transformationPipeLine.Add((input) => input.Trim());
transformationPipeLine.Add((input) => input.Substring(1));
transformationPipeLine.Add((input) => input.Substring(0, input.Length - 1));
transformationPipeLine.Add((input) => input.ToUpper());

var text = "    cat   ";
var output = transformationPipeLine.Aggregate(text, (input, transform)=> transform(input));
Console.WriteLine(output);

سيؤدي ذلك إلى إنشاء سلسلة من التحولات: إزالة المسافات البادئة واللاحقة -> إزالة الحرف الأول -> إزالة آخر حرف -> تحويل إلى الحالة العليا. يمكن إضافة الخطوات في هذه السلسلة أو إزالتها أو إعادة ترتيبها حسب الحاجة ، لإنشاء أي نوع من أنواع خطوط التحويل المطلوبة.

النتيجة النهائية لخط الأنابيب هذا ، هو أن " cat " يصبح "A" .

هذا يمكن أن تصبح قوية جدا بمجرد أن تدرك أن T يمكن أن يكون أي شيء . يمكن استخدام هذا BitMap الصور ، مثل عوامل التصفية ، باستخدام BitMap كمثال ؛


يعمل التجميع الفائق القصير مثل الطي في Haskell / ML / F #.

أطول قليلا .Max () ، .Min () ، .Sum () ، .Average () كل ما يتردد على العناصر في تسلسل وتجميعها باستخدام الدالة التجميعية الخاصة. .Aggregate () هو مجمّع معمم في أنه يسمح للمطور بتحديد حالة البدء (المسمى بالبذور) والوظيفة التجميعية.

أنا أعلم أنك طلبت شرحًا قصيرًا لكنني برزت بينما قدم الآخرون بضعة إجابات قصيرة فكنت أحسب أنك ربما تكون مهتمًا بمدة أطول قليلاً

نسخة طويلة مع الرمز إحدى الطرق لتوضيح ما يمكن أن يوضح كيفية تنفيذ نموذج الانحراف المعياري بمجرد استخدام foreach واستخدامه مرة واحدة. ملاحظة: لم أحدد أولوية الأداء هنا ، لذلك أكرر ذلك عدة مرات على المجموعة بدون داعٍ

أولاً ، تم استخدام دالة مساعد لإنشاء مجموع من المسافات التربيعية:

static double SumOfQuadraticDistance (double average, int value, double state)
{
    var diff = (value - average);
    return state + diff * diff;
}

ثم عينة الانحراف المعياري باستخدام ForEach:

static double SampleStandardDeviation_ForEach (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var state = seed;
    foreach (var value in ints)
    {
        state = SumOfQuadraticDistance (average, value, state);
    }
    var sumOfQuadraticDistance = state;

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

ثم مرة واحدة باستخدام.

static double SampleStandardDeviation_Aggregate (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var sumOfQuadraticDistance = ints
        .Aggregate (
            seed,
            (state, value) => SumOfQuadraticDistance (average, value, state)
            );

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

لاحظ أن هذه الوظائف متطابقة باستثناء كيف يتم حساب sumOfQuadraticDistance:

var state = seed;
foreach (var value in ints)
{
    state = SumOfQuadraticDistance (average, value, state);
}
var sumOfQuadraticDistance = state;

مقابل:

var sumOfQuadraticDistance = ints
    .Aggregate (
        seed,
        (state, value) => SumOfQuadraticDistance (average, value, state)
        );

فماذا يفعل. التجميع هو أنه يغلف نمط التجميع هذا ، وأتوقع أن يكون تنفيذ .Aggregate يبدو كالتالي:

public static TAggregate Aggregate<TAggregate, TValue> (
    this IEnumerable<TValue> values,
    TAggregate seed,
    Func<TAggregate, TValue, TAggregate> aggregator
    )
{
    var state = seed;

    foreach (var value in values)
    {
        state = aggregator (state, value);
    }

    return state;
}

قد يبدو استخدام وظائف الانحراف المعياري شيئًا كالتالي:

var ints = new[] {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
var average = ints.Average ();
var sampleStandardDeviation = ints.SampleStandardDeviation_Aggregate ();
var sampleStandardDeviation2 = ints.SampleStandardDeviation_ForEach ();

Console.WriteLine (average);
Console.WriteLine (sampleStandardDeviation);
Console.WriteLine (sampleStandardDeviation2);

IMHO

هل كذلك. المساعدة الكاملة في القراءة؟ بشكل عام أنا أحب LINQ لأنني أعتقد. أين ،. حدد ،. أودربي ، وهكذا يساعد كثيرا على القراءة (إذا كنت تجنب hierarhical يميل. اختيار). يجب أن يكون الإجمالي في Linq لأسباب كاملة ولكن أنا شخصياً لست مقتنعاً بأن .Aggregate يضيف قابلية القراءة مقارنةً بمعرفة foreach مكتوبة بشكل جيد.


صورة تساوي ألف كلمة

تذكير: Func<A, B, C> هي دالة ذات مدخلين من النوعين A و B ، وترجع C

Enumerable.Aggregate يحتوي على ثلاثة أحمال زائدة:


الزائد 1:

A Aggregate<A>(IEnumerable<A> a, Func<A, A, A> f)

مثال:

new[]{1,2,3,4}.Aggregate((x, y) => x + y);  // 10


هذا الحمل الزائد بسيط ، لكنه يحتوي على القيود التالية:

  • يجب أن يحتوي التسلسل على عنصر واحد على الأقل ،
    وإلا ستقوم الدالة بإلقاء InvalidOperationException .
  • العناصر والنتيجة يجب أن تكون من نفس النوع.


الزائد 2:

B Aggregate<A, B>(IEnumerable<A> a, B bIn, Func<B, A, B> f)

مثال:

var hayStack = new[] {"straw", "needle", "straw", "straw", "needle"};
var nNeedles = hayStack.Aggregate(0, (n, e) => e == "needle" ? n+1 : n);  // 2


هذا الحمل الزائد هو أكثر عمومية:

  • يجب توفير قيمة البذور ( bIn ).
  • يمكن أن تكون المجموعة فارغة ،
    في هذه الحالة ، ستؤدي الدالة قيمة البزر كنتيجة.
  • العناصر والنتيجة يمكن أن يكون لها أنواع مختلفة.


الحمل الزائد 3:

C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)


الحمل الزائد الثالث ليس IMO مفيدة جدا.
يمكن كتابة نفس الشيء بطريقة أكثر إيجازًا باستخدام الزائد 2 متبوعًا بوظيفة تحول نتيجة ذلك.


تم تكييف الرسوم التوضيحية من هذا blogpost الممتاز .


قد يكون التعريف القصير والأساسي هو: Linq Aggregate extension method يسمح بالإعلان عن نوع من الدالة العودية المطبقة على عناصر القائمة ، المعاملين منهم هما: العناصر بالترتيب الذي توجد به في القائمة ، عنصر واحد في كل مرة ، ونتيجة للتكرار التكراري السابق أو لا شيء إن لم يكن بعد recursion.

وبهذه الطريقة ، يمكنك حساب عامل الأرقام من الأرقام ، أو تسلسل السلاسل.


يتم استخدام التجميع بشكل أساسي لتجميع أو تجميع البيانات.

وفقًا لـ MSDN "الدالة التجميعية يقوم بتطبيق دالة accumulator عبر تسلسل."

مثال 1: إضافة كافة الأرقام في صفيف.

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate((total, nextValue) => total + nextValue);

* مهم: القيمة الإجمالية الأولية بشكل افتراضي هي العنصر 1 في تسلسل المجموعة. بمعنى: القيمة المبدئية المتغيرة الإجمالية ستكون 1 بشكل افتراضي.

تفسير متغير

الإجمالي: سيتم الاحتفاظ بقيمة مجموع القيمة (القيمة المجمعة) التي يتم إرجاعها بواسطة func.

nextValue: هي القيمة التالية في تسلسل الصفيف. يتم إضافة هذه القيمة إلى القيمة المجمعة ، أي إجمالي.

مثال 2: إضافة كافة العناصر في صفيف. حدد أيضًا قيمة المجمّع الأولي لبدء الإضافة من 10.

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);

الحجج الشرح:

الوسيطة الأولى هي القيمة الأولية (قيمة البداية مثل قيمة البذور) والتي سيتم استخدامها لبدء عملية إضافة مع القيمة التالية في الصفيف.

الحجة الثانية هي func وهو func يأخذ 2 int.

1.tutal: هذا سيحتفظ بنفس القيمة قبل قيمة المجموع (القيمة المجمعة) التي يتم إرجاعها بواسطة الدالة func بعد الحساب.

2.nextValue:: إنها القيمة التالية في تسلسل الصفيف. يتم إضافة هذه القيمة إلى القيمة المجمعة ، أي إجمالي.

كما سيوفر لك تصحيح هذا الرمز فهماً أفضل لكيفية عمل التجميع.


تجميع يستخدم لجمع الأعمدة في صفيف صحيح متعدد الأبعاد

        int[][] nonMagicSquare =
        {
            new int[] {  3,  1,  7,  8 },
            new int[] {  2,  4, 16,  5 },
            new int[] { 11,  6, 12, 15 },
            new int[] {  9, 13, 10, 14 }
        };

        IEnumerable<int> rowSums = nonMagicSquare
            .Select(row => row.Sum());
        IEnumerable<int> colSums = nonMagicSquare
            .Aggregate(
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + currentRow[index]).ToArray()
                );

يتم استخدام Select with index داخل Acregate func لملء الأعمدة المتطابقة وإرجاع صفيف جديد؛ {3 + 2 = 5، 1 + 4 = 5، 7 + 16 = 23، 8 + 5 = 13}.

        Console.WriteLine("rowSums: " + string.Join(", ", rowSums)); // rowSums: 19, 27, 44, 46
        Console.WriteLine("colSums: " + string.Join(", ", colSums)); // colSums: 25, 24, 45, 42

لكن حساب عدد التراتب في مجموعة منطقية أكثر صعوبة لأن النوع المتراكم (int) يختلف عن نوع المصدر (bool) ؛ هنا البذور ضروري من أجل استخدام الزائد الثاني.

        bool[][] booleanTable =
        {
            new bool[] { true, true, true, false },
            new bool[] { false, false, false, true },
            new bool[] { true, false, false, true },
            new bool[] { true, true, false, false }
        };

        IEnumerable<int> rowCounts = booleanTable
            .Select(row => row.Select(value => value ? 1 : 0).Sum());
        IEnumerable<int> seed = new int[booleanTable.First().Length];
        IEnumerable<int> colCounts = booleanTable
            .Aggregate(seed,
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + (currentRow[index] ? 1 : 0)).ToArray()
                );

        Console.WriteLine("rowCounts: " + string.Join(", ", rowCounts)); // rowCounts: 3, 1, 2, 2
        Console.WriteLine("colCounts: " + string.Join(", ", colCounts)); // colCounts: 3, 2, 1, 2

إن تعريف Aggregate الأسهل لفهم هو أنها تقوم بإجراء عملية على كل عنصر من عناصر القائمة مع الأخذ بعين الاعتبار العمليات التي تم تنفيذها من قبل. بمعنى أنه ينفذ الإجراء على العنصر الأول والثاني ويحمل النتيجة إلى الأمام. ثم يعمل على النتيجة السابقة والعنصر الثالث وينقل إلى الأمام. إلخ

مثال 1. تلخيص الأرقام

var nums = new[]{1,2,3,4};
var sum = nums.Aggregate( (a,b) => a + b);
Console.WriteLine(sum); // output: 10 (1+2+3+4)

هذا يضيف 1 و 2 لجعل 3 . ثم يضيف 3 (نتيجة من السابق) و 3 (العنصر التالي في التسلسل) لجعل 6 . ثم يضيف 6 و 4 لجعل 10 .

مثال 2. إنشاء csv من صفيف من السلاسل

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate( (a,b) => a + ',' + b);
Console.WriteLine(csv); // Output a,b,c,d

هذا يعمل بالطريقة نفسها. سلسل فاصلة وباء لجعل a,b . ثم يسلسل a,b بفاصلة و c لجعل a,b,c . وما إلى ذلك وهلم جرا.

المثال 3. ضرب الأرقام باستخدام البذور

للتأكد من اكتمالها ، هناك overload من Aggregate الذي يأخذ قيمة البذور.

var multipliers = new []{10,20,30,40};
var multiplied = multipliers.Aggregate(5, (a,b) => a * b);
Console.WriteLine(multiplied); //Output 1200000 ((((5*10)*20)*30)*40)

يشبه إلى حد كبير الأمثلة المذكورة أعلاه ، يبدأ هذا بقيمة 5 ويضاعفها بواسطة العنصر الأول في التسلسل 10 مما يعطي نتيجة 50 . يتم ترحيل هذه النتيجة وضربها بالرقم التالي في التسلسل 20 لإعطاء نتيجة 1000 . يستمر هذا من خلال العنصر 2 المتبقي من التسلسل.

أمثلة حية: http://rextester.com/ZXZ64749
المستندات: http://msdn.microsoft.com/en-us/library/bb548651.aspx

إضافة

يستخدم المثال 2 ، أعلاه ، سلسلة string لإنشاء قائمة قيم مفصولة بفاصلة. هذه طريقة مبسطة لشرح استخدام Aggregate الذي كان القصد من هذه الإجابة. ومع ذلك ، في حالة استخدام هذه التقنية لإنشاء كمية كبيرة من البيانات المفصولة بفواصل ، سيكون من الأنسب استخدام StringBuilder ، وهذا متوافق تمامًا مع Aggregate باستخدام التحميل الزائد المصنف لبدء برنامج StringBuilder .

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate(new StringBuilder(), (a,b) => {
    if(a.Length>0)
        a.Append(",");
    a.Append(b);
    return a;
});
Console.WriteLine(csv);

مثال محدث: http://rextester.com/YZCVXV6464


هذا تفسير حول استخدام Aggregate على API Fluent مثل Linq الفرز.

var list = new List<Student>();
var sorted = list
    .OrderBy(s => s.LastName)
    .ThenBy(s => s.FirstName)
    .ThenBy(s => s.Age)
    .ThenBy(s => s.Grading)
    .ThenBy(s => s.TotalCourses);

وسترى أننا نريد تنفيذ وظيفة فرز تأخذ مجموعة من الحقول ، وهذا أمر سهل للغاية باستخدام Aggregate بدلاً من حلقة for-loop ، كما يلي:

public static IOrderedEnumerable<Student> MySort(
    this List<Student> list,
    params Func<Student, object>[] fields)
{
    var firstField = fields.First();
    var otherFields = fields.Skip(1);

    var init = list.OrderBy(firstField);
    return otherFields.Skip(1).Aggregate(init, (resultList, current) => resultList.ThenBy(current));
}

ويمكننا استخدامه على هذا النحو:

var sorted = list.MySort(
    s => s.LastName,
    s => s.FirstName,
    s => s.Age,
    s => s.Grading,
    s => s.TotalCourses);

أولا تثبيت أدوات ديناميكية -> NuGet مدير الحزم -> مدير وحدة التحكم

install-package System.Linq.Dynamic

إضافة Namespace using System.Linq.Dynamic;

الآن يمكنك استخدام OrderBy("Name, Age DESC")





c# .net linq