c# - remove - htmlagilitypack




Algoritmo di aggregazione LINQ spiegato (8)

Un'immagine vale più di mille parole

Promemoria: Func<A, B, C> è una funzione con due ingressi di tipo A e B , che restituisce una C

Enumerable.Aggregate ha tre overload:


Sovraccarico 1:

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

Esempio:

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


Questo sovraccarico è semplice, ma presenta le seguenti limitazioni:

  • la sequenza deve contenere almeno un elemento,
    altrimenti la funzione genererà una InvalidOperationException .
  • gli elementi e il risultato devono essere dello stesso tipo.


Sovraccarico 2:

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

Esempio:

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


Questo sovraccarico è più generale:

  • deve essere fornito un valore di seme ( bIn ).
  • la raccolta può essere vuota,
    in questo caso, la funzione produrrà il valore seme come risultato.
  • elementi e risultati possono avere diversi tipi.


Sovraccarico 3:

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


Il terzo sovraccarico non è molto utile IMO.
Lo stesso può essere scritto in modo più sintetico usando l'overload 2 seguito da una funzione che trasforma il suo risultato.


Le illustrazioni sono adattate da questo eccellente blogpost .

Questo potrebbe sembrare zoppo, ma non sono stato in grado di trovare una spiegazione veramente buona di Aggregate .

Buono significa breve, descrittivo, completo con un esempio piccolo e chiaro.


Aggregazione utilizzata per sommare le colonne in un array intero multidimensionale

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

Selezionare con indice viene utilizzato all'interno della funzione Aggregazione per sommare le colonne corrispondenti e restituire una nuova matrice; {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

Ma il conteggio del numero di trues in un array booleano è più difficile poiché il tipo accumulato (int) differisce dal tipo di origine (bool); qui è necessario un seme per usare il secondo sovraccarico.

        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

L'aggregato è fondamentalmente utilizzato per raggruppare o sommare i dati.

Secondo MSDN "Funzione Aggrega Applica una funzione di accumulatore su una sequenza."

Esempio 1: aggiungi tutti i numeri in una matrice.

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

* importante: il valore di aggregazione iniziale di default è l'elemento 1 nella sequenza di raccolta. vale a dire: il valore iniziale della variabile totale sarà 1 per impostazione predefinita.

spiegazione variabile

totale: manterrà il valore di somma (valore aggregato) restituito dal func.

nextValue: è il valore successivo nella sequenza dell'array. Questo valore viene aggiunto al valore aggregato, ovvero totale.

Esempio 2: aggiungere tutti gli elementi in una matrice. Imposta anche il valore dell'accumulatore iniziale per iniziare ad aggiungere con da 10.

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

spiegazione degli argomenti:

il primo argomento è l'iniziale (valore iniziale cioè valore iniziale) che verrà utilizzato per iniziare l'aggiunta con il valore successivo nell'array.

il secondo argomento è una funzione che è una funzione che prende 2 int.

1. totale: verrà mantenuto come prima del valore di somma (valore aggregato) restituito dalla funzione dopo il calcolo.

2.nextValue:: è il valore successivo nella sequenza dell'array. Questo valore viene aggiunto al valore aggregato, ovvero totale.

Anche il debug di questo codice ti darà una migliore comprensione di come funziona il lavoro di aggregazione.


La definizione più semplice di Aggregate è che esegue un'operazione su ciascun elemento dell'elenco tenendo conto delle operazioni precedenti. Ciò significa che esegue l'azione sul primo e sul secondo elemento e porta avanti il ​​risultato. Quindi opera sul risultato precedente e sul terzo elemento e porta avanti. eccetera.

Esempio 1. Numeri di somma

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

Questo aggiunge 1 e 2 per fare 3 . Quindi aggiunge 3 (risultato del precedente) e 3 (il prossimo elemento in sequenza) per fare 6 . Quindi aggiunge 6 e 4 per fare 10 .

Esempio 2. creare un csv da una matrice di stringhe

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

Funziona allo stesso modo. Concatena a virgola b per creare a,b . Quindi concatena a,b con una virgola c per creare a,b,c . e così via.

Esempio 3. Moltiplicare numeri usando un seme

Per completezza, c'è un overload di Aggregate che prende un valore di inizializzazione.

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)

Molto simile agli esempi precedenti, questo inizia con un valore di 5 e lo moltiplica per il primo elemento della sequenza 10 dando un risultato di 50 . Questo risultato viene portato avanti e moltiplicato per il numero successivo nella sequenza 20 per dare un risultato di 1000 . Questo continua attraverso il rimanente 2 elemento della sequenza.

Esempi live: http://rextester.com/ZXZ64749
Documenti: http://msdn.microsoft.com/en-us/library/bb548651.aspx

appendice

L'Esempio 2, sopra, utilizza la concatenazione di stringhe per creare un elenco di valori separati da una virgola. Questo è un modo semplicistico per spiegare l'uso di Aggregate che era l'intenzione di questa risposta. Tuttavia, se si utilizza questa tecnica per creare effettivamente una grande quantità di dati separati da virgole, sarebbe più appropriato utilizzare un oggetto StringBuilder , e questo è interamente compatibile con Aggregate utilizza il sovraccarico seminato per avviare 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);

Esempio aggiornato: http://rextester.com/YZCVXV6464


Oltre a tutte le grandi risposte qui già, l'ho anche usato per camminare un oggetto attraverso una serie di passaggi di trasformazione.

Se una trasformazione è implementata come Func<T,T> , è possibile aggiungere diverse trasformazioni a un List<Func<T,T>> e utilizzare Aggregate per percorrere un'istanza di T attraverso ogni passaggio.

Un esempio più concreto

Si desidera prendere un valore string e passarlo attraverso una serie di trasformazioni di testo che potrebbero essere costruite in modo programmatico.

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

Questo creerà una catena di trasformazioni: rimuovi spazi iniziali e finali -> rimuovi il primo carattere -> rimuovi l'ultimo carattere -> converti in maiuscolo. Le fasi di questa catena possono essere aggiunte, rimosse o riordinate secondo necessità, per creare qualunque tipo di pipeline di trasformazione sia richiesta.

Il risultato finale di questa specifica pipeline è che " cat " diventa "A" .

Questo può diventare molto potente una volta che ti rendi conto che T può essere qualsiasi cosa . Questo potrebbe essere usato per trasformazioni di immagini, come filtri, usando BitMap come esempio;


Questa è una spiegazione sull'uso di Aggregate su un'API fluente come l'ordinamento 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);

e vediamo che vogliamo implementare una funzione di ordinamento che accetta un insieme di campi, questo è molto semplice usando Aggregate invece di un ciclo for, come questo:

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

E possiamo usarlo in questo modo:

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

Super short Aggregate funziona come fold in Haskell / ML / F #.

Leggermente più lungo .Max (), .Min (), .Sum (), .Average () esegue iterazioni sugli elementi di una sequenza e li aggrega utilizzando la rispettiva funzione di aggregazione. .Aggregate () è un aggregatore generalizzato in quanto consente allo sviluppatore di specificare lo stato di avvio (aka seed) e la funzione di aggregazione.

So che hai chiesto una breve spiegazione, ma ho pensato che dato che altri hanno dato un paio di risposte brevi ho pensato che forse ti sarebbe interessato un po 'più lungo

Versione lunga con codice Un modo per illustrare cosa potrebbe essere mostrare come si implementa la deviazione standard del campione una volta utilizzando foreach e una volta utilizzando .Aggregate. Nota: non ho dato la priorità alle prestazioni qui, quindi eseguo più volte l'iterazione sulla raccolta inutilmente

Innanzitutto una funzione di supporto utilizzata per creare una somma di distanze quadratiche:

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

Quindi, deviazione standard del campione utilizzando 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));
}

Quindi una volta usando .Aggregate:

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

Si noti che queste funzioni sono identiche tranne per quanto viene calcolato sumOfQuadraticDistance:

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

Contro:

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

Quindi, ciò che fa un aggregato è che incapsula questo schema di aggregazione e mi aspetto che l'implementazione di .Aggregate assomigli a qualcosa del genere:

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

L'uso delle funzioni di deviazione standard sarebbe simile a questo:

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

A parer mio

Così.. Aiuta a migliorare la leggibilità? In generale, amo LINQ perché penso. Dove, .Seleziona, .OrderBy e così via aiuta notevolmente la leggibilità (se eviti i gerarchici selettivi .Select). Gli aggregati devono essere presenti in Linq per motivi di completezza, ma personalmente non ne sono altrettanto convinto. Aggiunta aggiunge leggibilità rispetto a una foreach ben scritta.


Jamiec's imparato molto dalla risposta Jamiec's .

Se l'unica necessità è generare una stringa CSV, puoi provare questo.

var csv3 = string.Join(",",chars);

Ecco un test con 1 milione di stringhe

0.28 seconds = Aggregate w/ String Builder 
0.30 seconds = String.Join 

Il codice sorgente è here





linq