c# strip - Algoritmo di aggregazione LINQ spiegato




html agility (10)

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.


Answers

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

Ognuno ha dato la sua spiegazione. La mia spiegazione è così.

Il metodo aggregato applica una funzione a ciascun elemento di una raccolta. Ad esempio, abbiamo la collection {6, 2, 8, 3} e la funzione Add (operator +) che fa (((6 + 2) +8) +3) e restituisce 19

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: (result, item) => result + item);
// sum: (((6+2)+8)+3) = 19

In questo esempio viene passato il metodo named Add anziché l'espressione lambda.

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: Add);
// sum: (((6+2)+8)+3) = 19

private static int Add(int x, int y) { return x + y; }

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


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;


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 .


In parte dipende da quale sovraccarico stai parlando, ma l'idea di base è:

  • Inizia con un seme come "valore corrente"
  • Scorrere la sequenza. Per ogni valore nella sequenza:
    • Applicare una funzione specificata dall'utente per trasformare (currentValue, sequenceValue) in (nextValue)
    • Imposta currentValue = nextValue
  • Restituisce il valore currentValue finale

È possibile trovare utile il post Aggregate nella mia serie Edulinq, che include una descrizione più dettagliata (compresi i vari overload) e le implementazioni.

Un semplice esempio utilizza Aggregate come alternativa al 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);

O forse sommando tutte le lunghezze delle stringhe in una sequenza di stringhe:

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

Personalmente raramente trovo Aggregate utile - i metodi di aggregazione "su misura" di solito sono abbastanza buoni per me.


Una definizione breve ed essenziale potrebbe essere questa: il metodo di estensione di Linq Aggregate consente di dichiarare una sorta di funzione ricorsiva applicata agli elementi di una lista, gli operandi di chi sono due: gli elementi nell'ordine in cui sono presenti nella lista, un elemento alla volta e il risultato della precedente iterazione ricorsiva o nulla se non ancora ricorsione.

In questo modo puoi calcolare il fattoriale dei numeri o concatenare le stringhe.


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.


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.


provare

persons.GroupBy(x => x.PersonId).Select(x => x)

o

per verificare se qualcuno sta ripetendo nel tuo elenco prova

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)




c# .net linq