Un modo elegante per combinare più raccolte di elementi?


94

Supponiamo che io disponga di un numero arbitrario di raccolte, ciascuna contenente oggetti dello stesso tipo (ad esempio List<int> fooe List<int> bar). Se queste raccolte fossero esse stesse in una raccolta (ad esempio, di tipo List<List<int>>, potrei usarle SelectManyper combinarle tutte in una raccolta.

Tuttavia, se queste raccolte non sono già nella stessa raccolta, è mia impressione che dovrei scrivere un metodo come questo:

public static IEnumerable<T> Combine<T>(params ICollection<T>[] toCombine)
{
   return toCombine.SelectMany(x => x);
}

Che poi chiamerei così:

var combined = Combine(foo, bar);

Esiste un modo pulito ed elegante per combinare (un numero qualsiasi di) raccolte senza dover scrivere un metodo di utilità come Combinesopra? Sembra abbastanza semplice che dovrebbe esserci un modo per farlo in LINQ, ma forse no.



Attenzione a Concat per la concatenazione di un numero molto elevato di enumerabili, potrebbe far saltare in aria il tuo stack: programmaticallyspeaking.com/…
nawfal

Risposte:


107

Penso che potresti cercare LINQ .Concat()?

var combined = foo.Concat(bar).Concat(foobar).Concat(...);

In alternativa, .Union()rimuoverà gli elementi duplicati.


2
Grazie; L'unico motivo per cui non l'ho fatto è che (a me) sembra diventare più brutto con più collezioni che devi affrontare. Tuttavia, questo ha il vantaggio di utilizzare le funzioni LINQ esistenti, che probabilmente i futuri sviluppatori avranno già familiarità.
Ciambella

2
Grazie per il .Union()suggerimento. Tieni presente che dovrai implementare IComparer sul tuo tipo personalizzato nel caso in cui lo sia.
Gonzo345

29

Per me Concatcome metodo di estensione non è molto elegante nel mio codice quando ho più sequenze di grandi dimensioni da concatenare. Questo è semplicemente un problema di indentazione / formattazione del codice e qualcosa di molto personale.

Certo che sembra buono così:

var list = list1.Concat(list2).Concat(list3);

Non così leggibile quando si legge come:

var list = list1.Select(x = > x)
   .Concat(list2.Where(x => true)
   .Concat(list3.OrderBy(x => x));

O quando sembra:

return Normalize(list1, a, b)
    .Concat(Normalize(list2, b, c))
       .Concat(Normalize(list3, c, d));

o qualunque sia la tua formattazione preferita. Le cose peggiorano con concatenazioni più complesse. La ragione della mia sorta di dissonanza cognitiva con lo stile di cui sopra è che la prima sequenza si trova al di fuori del Concatmetodo mentre le sequenze successive si trovano all'interno. Preferisco chiamare Concatdirettamente il metodo statico e non lo stile di estensione:

var list = Enumerable.Concat(list1.Select(x => x),
                             list2.Where(x => true));

Per più numero di concatenazioni di sequenze porto lo stesso metodo statico di OP:

public static IEnumerable<T> Concat<T>(params IEnumerable<T>[] sequences)
{
    return sequences.SelectMany(x => x);
}

Quindi posso scrivere:

return EnumerableEx.Concat
(
    list1.Select(x = > x),
    list2.Where(x => true),
    list3.OrderBy(x => x)
);

Sembra migliore. Il nome della classe extra, altrimenti ridondante, che devo scrivere non è un problema per me, considerando che le mie sequenze sembrano più pulite con la Concatchiamata. È meno un problema in C # 6 . Puoi semplicemente scrivere:

return Concat(list1.Select(x = > x),
              list2.Where(x => true),
              list3.OrderBy(x => x));

Avremmo voluto avere gli operatori di concatenazione degli elenchi in C #, qualcosa come:

list1 @ list2 // F#
list1 ++ list2 // Scala

Molto più pulito in questo modo.


4
Apprezzo la tua preoccupazione per lo stile di codifica. Questo è molto più elegante.
Bryan Rayner

Forse una versione più piccola della tua risposta può essere unita alla risposta principale qui.
Bryan Rayner

2
Mi piace questa formattazione, come pure - anche se preferisco per eseguire le operazioni sulle liste individuale (ad esempio Where, OrderBy) prima per chiarezza, soprattutto se sono qualcosa di più complesso di questo esempio.
brichins

27

Per il caso quando si fare avere una collezione di collezioni, cioè una List<List<T>>, Enumerable.Aggregateè un modo più elegante per combinare tutte le liste in una sola:

var combined = lists.Aggregate((acc, list) => { return acc.Concat(list); });

1
Come si ottiene listsprima qui? Questo è il primo problema che ha OP. Se ha avuto un collection<collection>inizio con allora SelectManyè solo molto più semplice.
nawfal

Non sono sicuro di cosa sia successo, ma questo sembra rispondere alla domanda sbagliata. Risposta modificata per riflettere questo. Molte persone sembrano finire qui perché la necessità di combinare una lista <Lista <T>>
astreltsov



7

Puoi sempre usare Aggregate combinato con Concat ...

        var listOfLists = new List<List<int>>
        {
            new List<int> {1, 2, 3, 4},
            new List<int> {5, 6, 7, 8},
            new List<int> {9, 10}
        };

        IEnumerable<int> combined = new List<int>();
        combined = listOfLists.Aggregate(combined, (current, list) => current.Concat(list)).ToList();

1
La migliore soluzione finora.
Oleg

@Oleg SelectManyè solo più semplice.
nawfal

6

L'unico modo in cui vedo è usare Concat()

 var foo = new List<int> { 1, 2, 3 };
 var bar = new List<int> { 4, 5, 6 };
 var tor = new List<int> { 7, 8, 9 };

 var result = foo.Concat(bar).Concat(tor);

Ma dovresti decidere cosa è meglio:

var result = Combine(foo, bar, tor);

o

var result = foo.Concat(bar).Concat(tor);

Un punto per cui Concat()sarà una scelta migliore è che sarà più ovvio per un altro sviluppatore. Più leggibile e semplice.


3

Puoi usare Union come segue:

var combined=foo.Union(bar).Union(baz)...

Tuttavia, questo rimuoverà gli elementi identici, quindi se li hai, potresti volerli usare Concat, invece.


4
No! Enumerable.Unionè un'unione di insiemi che non produce il risultato desiderato (produce duplicati solo una volta).
jason

Sì, ho bisogno delle istanze specifiche degli oggetti, poiché ho bisogno di eseguire un'elaborazione speciale su di essi. L'utilizzo intnel mio esempio potrebbe aver sconcertato un po 'le persone; ma Unionnon funzionerà per me in questo caso.
Ciambella

2

Un paio di tecniche che utilizzano gli inizializzatori di raccolta:

assumendo questi elenchi:

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };

SelectMany con un inizializzatore di array (non proprio così elegante per me, ma non si basa su alcuna funzione di supporto):

var combined = new []{ list1, list2 }.SelectMany(x => x);

Definire un'estensione elenco per Aggiungi che consenta IEnumerable<T>inList<T> inizializzatore :

public static class CollectionExtensions
{
    public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items) collection.Add(item);
    }
}

Quindi puoi creare un nuovo elenco contenente gli elementi degli altri in questo modo (consente anche di mescolare singoli elementi).

var combined = new List<int> { list1, list2, 7, 8 };

1

Dato che stai iniziando con un mucchio di raccolte separate, penso che la tua soluzione sia piuttosto elegante. Dovrai fare qualcosa per unirli insieme.

Sarebbe più conveniente sintatticamente creare un metodo di estensione dal tuo metodo Combine, che lo renderebbe disponibile ovunque tu vada.


Mi piace l'idea del metodo di estensione, semplicemente non volevo reinventare la ruota se LINQ avesse già un metodo che avrebbe fatto la stessa cosa (ed essere immediatamente comprensibile agli altri sviluppatori più avanti)
Donut

1

Tutto ciò di cui hai bisogno è questo, per qualsiasi IEnumerable<IEnumerable<T>> lists:

var combined = lists.Aggregate((l1, l2) => l1.Concat(l2));

Questo combinerà tutti gli elementi listsin uno IEnumerable<T>(con duplicati). Utilizzare Unioninvece di Concatper rimuovere i duplicati, come indicato nelle altre risposte.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.