C #: un modo elegante per partizionare una lista?

Mi piacerebbe partizionare una lista in una lista di liste, specificando il numero di elementi in ogni partizione.

Per esempio, supponiamo di avere la lista {1, 2, … 11}, e vorrei partizionarla in modo tale che ogni insieme abbia 4 elementi, con l’ultimo set che riempie quanti più elementi ansible. La partizione risultante sarebbe simile a {{1..4}, {5..8}, {9..11}}

Quale sarebbe un modo elegante di scrivere questo?

Ecco un metodo di estensione che farà ciò che desideri:

public static IEnumerable> Partition(this IList source, Int32 size) { for (int i = 0; i < (source.Count / size) + (source.Count % size > 0 ? 1 : 0); i++) yield return new List(source.Skip(size * i).Take(size)); } 

Modifica: ecco una versione molto più pulita della funzione:

 public static IEnumerable> Partition(this IList source, Int32 size) { for (int i = 0; i < Math.Ceiling(source.Count / (Double)size); i++) yield return new List(source.Skip(size * i).Take(size)); } 

Usando LINQ puoi tagliare i tuoi gruppi in un’unica riga di codice come questo …

 var x = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; var groups = x.Select((i, index) => new { i, index }).GroupBy(group => group.index / 4, element => element.i); 

Potresti quindi scorrere i gruppi come il seguente …

 foreach (var group in groups) { Console.WriteLine("Group: {0}", group.Key); foreach (var item in group) { Console.WriteLine("\tValue: {0}", item); } } 

e otterrai un risultato simile a questo …

 Group: 0 Value: 1 Value: 2 Value: 3 Value: 4 Group: 1 Value: 5 Value: 6 Value: 7 Value: 8 Group: 2 Value: 9 Value: 10 Value: 11 

Qualcosa come (codice aereo non testato):

 IEnumerable> PartitionList(IList list, int maxCount) { List partialList = new List(maxCount); foreach(T item in list) { if (partialList.Count == maxCount) { yield return partialList; partialList = new List(maxCount); } partialList.Add(item); } if (partialList.Count > 0) yield return partialList; } 

Ciò restituisce un’enumerazione di elenchi anziché un elenco di elenchi, ma puoi facilmente racchiudere il risultato in un elenco:

 IList> listOfLists = new List(PartitionList(list, maxCount)); 

Per evitare il raggruppamento, la matematica e la reiterazione.

 IEnumerable> Partition(this IEnumerable source, int size) { var partition = new T[size]; var count = 0; foreach(var t in source) { partition[count] = t; count ++; if (count == size) { yield return partition; var partition = new T[size]; var count = 0; } } if (count > 0) { Array.Resize(ref partition, count); yield return partition; } } 
 var yourList = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; var groupSize = 4; // here's the actual query that does the grouping... var query = yourList .Select((x, i) => new { x, i }) .GroupBy(i => ii / groupSize, x => xx); // and here's a quick test to ensure that it worked properly... foreach (var group in query) { foreach (var item in group) { Console.Write(item + ","); } Console.WriteLine(); } 

Se è necessario un List> effettivo List> anziché un object IEnumerable> modificare la query nel modo seguente:

 var query = yourList .Select((x, i) => new { x, i }) .GroupBy(i => ii / groupSize, x => xx) .Select(g => g.ToList()) .ToList(); 

Oppure in .Net 2.0 si dovrebbe fare questo:

  static void Main(string[] args) { int[] values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; List items = new List(SplitArray(values, 4)); } static IEnumerable SplitArray(T[] items, int size) { for (int index = 0; index < items.Length; index += size) { int remains = Math.Min(size, items.Length-index); T[] segment = new T[remains]; Array.Copy(items, index, segment, 0, remains); yield return segment; } } 
 public static IEnumerable> Partition(this IEnumerable list, int size) { while (list.Any()) { yield return list.Take(size); list = list.Skip(size); } } 

e per il caso speciale di String

 public static IEnumerable Partition(this string str, int size) { return str.Partition(size).Select(AsString); } public static string AsString(this IEnumerable charList) { return new string(charList.ToArray()); } 

Potresti usare un metodo di estensione:

 public static IList > Partition  (questo input IEnumerable , Func  partitionFunc)
 {
       Dizionario  partitions = new Dictionary > (); 

  object currentKey = null; foreach (T item in input ?? Enumerable.Empty()) { currentKey = partitionFunc(item); if (!partitions.ContainsKey(currentKey)) { partitions[currentKey] = new HashSet(); } partitions[currentKey].Add(item); } return partitions.Values.ToList(); 

}

L’utilizzo di ArraySegments potrebbe essere una soluzione leggibile e breve (è necessario il cast della lista su array):

 var list = new List() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; //Added 0 in front on purpose in order to enhance simplicity. int[] array = list.ToArray(); int step = 4; List listSegments = new List(); for(int i = 0; i < array.Length; i+=step) { int[] segment = new ArraySegment(array, i, step).ToArray(); listSegments.Add(segment); } 

Non sono sicuro del motivo per cui Jochems ha risposto usando ArraySegment è stato votato. Potrebbe essere davvero utile fino a quando non avrai bisogno di estendere i segmenti (cast in IList). Ad esempio, immagina che ciò che stai cercando di fare è passare i segmenti in una pipeline TPL DataFlow per l’elaborazione simultanea. Passare i segmenti come istanze di IList consente allo stesso codice di gestire gli array e le liste in modo agnostico.

Naturalmente, questo solleva la domanda: perché non derivare una class ListSegment che non richiede una perdita di memoria chiamando ToArray ()? La risposta è che gli array possono essere elaborati in modo leggermente più veloce in alcune situazioni (indicizzazione leggermente più veloce). Ma dovresti fare un po ‘di elaborazione abbastanza hardcore per notare molta differenza. Ancora più importante, non c’è un buon modo per proteggere da inserimenti casuali e rimuovere le operazioni da un altro codice in possesso di un riferimento alla lista.

Calling ToArray () su un elenco numerico di milioni di valori impiega circa 3 millisecondi sulla mia workstation. Di solito non è un prezzo troppo alto da pagare quando lo si utilizza per ottenere i vantaggi di una sicurezza thread più affidabile nelle operazioni simultanee, senza incorrere nel pesante costo del blocco.

Per evitare controlli multipli, istanze non necessarie e iterazioni ripetitive, è ansible utilizzare il codice:

 namespace System.Collections.Generic { using Linq; using Runtime.CompilerServices; public static class EnumerableExtender { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsEmpty(this IEnumerable enumerable) => !enumerable?.GetEnumerator()?.MoveNext() ?? true; public static IEnumerable> Partition(this IEnumerable source, int size) { if (source == null) throw new ArgumentNullException(nameof(source)); if (size < 2) throw new ArgumentOutOfRangeException(nameof(size)); IEnumerable items = source; IEnumerable partition; while (true) { partition = items.Take(size); if (partition.IsEmpty()) yield break; else yield return partition; items = items.Skip(size); } } } }