2012-07-13 38 views
96

Sto tentando di dividere una lista in una serie di elenchi più piccoli.Dividere una lista in elenchi più piccoli di dimensione N

Il mio problema: La mia funzione di dividere gli elenchi non li suddivide in elenchi di dimensioni corrette. Dovrebbe dividerli in elenchi di dimensioni 30 ma invece li divide in elenchi di dimensione 114?

Come posso rendere la mia funzione dividere un elenco in X numero di elenchi di dimensioni 30 o meno?

public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30) 
{  
    List<List<float[]>> list = new List<List<float[]>>(); 

    for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) { 
     List <float[]> subLocat = new List <float[]>(locations); 

     if (subLocat.Count >= ((i*nSize)+nSize)) 
      subLocat.RemoveRange(i*nSize, nSize); 
     else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize)); 

     Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString()); 
     list.Add (subLocat); 
    } 

    return list; 
} 

Se uso la funzione in un elenco di dimensioni 144 allora l'uscita è:

Index: 4, Dimensioni: 120
Index: 3, formato: 114
Index: 2 , Dimensione: 114
Index: 1, formato: Index 114
: 0, Dimensioni: 114

+1

Se una soluzione LINQ è accettabile, [questa domanda può essere di qualche aiuto] (http://stackoverflow.com/questions/419019/split -list-in-sottoliste-con-LINQ). –

+0

In particolare la risposta di Sam Saffron a quella domanda precedente. E a meno che non si tratti di un incarico scolastico, vorrei solo usare il suo codice e fermarmi. – jcolebrand

risposta

110
public static List<List<float[]>> splitList(List<float[]> locations, int nSize=30) 
{   
    var list = new List<List<float[]>>(); 

    for (int i=0; i < locations.Count; i+= nSize) 
    { 
     list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
    } 

    return list; 
} 

versione generica:

public static IEnumerable<List<T>> splitList<T>(List<T> locations, int nSize=30) 
{   
    for (int i=0; i < locations.Count; i+= nSize) 
    { 
     yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); 
    } 
} 
+1

Loveee 'yield return' – lostmylogin

+0

Quindi se ho una lunghezza di lista zillion, e voglio dividere in liste più piccole lunghezza 30, e da ogni lista più piccola voglio solo prendere (1), quindi creo ancora liste di 30 elementi di che butto via 29 oggetti. Questo può essere fatto in modo più intelligente! –

24

come circa:

while(locations.Any()) 
{  
    list.Add(locations.Take(nSize).ToList()); 
    locations= locations.Skip(nSize).ToList(); 
} 
+0

Sta andando a consumare molta memoria? Ogni volta che accade locations.Skip.ToList mi chiedo se viene allocata più memoria e gli articoli non selezionati sono referenziati da una nuova lista. – Zasz

+0

sì nuovo elenco viene creato su ogni ciclo. Sì, consuma memoria. Tuttavia, se si verificano problemi di memoria, non è questo il luogo da ottimizzare in quanto le istanze di tali elenchi sono pronte per essere raccolte al ciclo successivo. Puoi scambiare le prestazioni per la memoria saltando la 'ToList', ma non mi preoccuperei di cercare di ottimizzarla - è così banale e improbabile che sia un collo di bottiglia. Il guadagno principale di questa implementazione è la sua banalità, è facile da capire. Se vuoi puoi usare la risposta accettata non crea quelle liste ma è un po 'più complesso. – Rafal

+0

'.Skip (n)' scorre su 'n' elementi ogni volta che viene chiamato, mentre questo può essere ok, è importante considerare per il codice critico delle prestazioni. http://stackoverflow.com/questions/20002975/performance-of-skip-and-similar-functions-like-take – Chakrava

5

Ho un metodo generico che avrebbe preso qualsiasi tipo comprendono galleggiante, ed è stato unità testato, la speranza aiuta:

/// <summary> 
    /// Breaks the list into groups with each group containing no more than the specified group size 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="values">The values.</param> 
    /// <param name="groupSize">Size of the group.</param> 
    /// <returns></returns> 
    public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null) 
    { 
     List<List<T>> result = new List<List<T>>(); 
     // Quick and special scenario 
     if (values.Count() <= groupSize) 
     { 
      result.Add(values.ToList()); 
     } 
     else 
     { 
      List<T> valueList = values.ToList(); 
      int startIndex = 0; 
      int count = valueList.Count; 
      int elementCount = 0; 

      while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount))) 
      { 
       elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize; 
       result.Add(valueList.GetRange(startIndex, elementCount)); 
       startIndex += elementCount; 
      } 
     } 


     return result; 
    } 
+0

Grazie. Mi chiedo se è possibile aggiornare i commenti con la definizione del parametro maxCount? Una rete di sicurezza? –

222

Vorrei suggerire di utilizzare questo metodo di estensione per ridurre l'elenco di sorgenti alle sotto-liste per dimensione del blocco specificato:

/// <summary> 
/// Helper methods for the lists. 
/// </summary> 
public static class ListExtensions 
{ 
    public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize) 
    { 
     return source 
      .Select((x, i) => new { Index = i, Value = x }) 
      .GroupBy(x => x.Index/chunkSize) 
      .Select(x => x.Select(v => v.Value).ToList()) 
      .ToList(); 
    } 
} 

Ad esempio, se si esegue il chuck dell'elenco di 18 elementi per 5 elementi per blocco, viene fornito l'elenco di 4 sottoliste con i seguenti elementi all'interno: 5-5-5-3.

+7

soluzione impressionante – MonsterMMORPG

+3

Prima di utilizzarlo in produzione, assicurati di capire quali sono le implicazioni in termini di runtime per memoria e prestazioni. Solo perché LINQ può essere sintetico, non significa che sia una buona idea. – Nick

+3

Definitivamente, @Nick vorrei suggerire in generale di pensare prima di fare qualsiasi cosa. Chunking con LINQ non dovrebbe essere un'operazione frequente ripetuta migliaia di volte. Di solito è necessario un elenco di blocchi per l'elaborazione degli articoli batch per batch e/o in parallelo. –

9

soluzione Serj-Tm va bene, anche questa è la versione generica come metodo di estensione per le liste (metterlo in una classe statica):

public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30) 
{ 
    List<List<T>> list = new List<List<T>>(); 
    for (int i = 0; i < items.Count; i += sliceSize) 
     list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i))); 
    return list; 
} 
6

trovo risposta accettata (Serj-Tm) più robusto, ma mi piacerebbe suggerire una versione generica.

public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30) 
    { 
     var list = new List<List<T>>(); 

     for (int i = 0; i < locations.Count; i += nSize) 
     { 
      list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
     } 

     return list; 
    } 
1

Biblioteca MoreLinq hanno chiamato metodo Batch

List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements 
int counter = 1; 
foreach(var batch in ids.Batch(2)) 
{ 
    foreach(var eachId in batch) 
    { 
     Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId); 
    } 
    counter++; 
} 

risultato è

Batch: 1, Id: 1 
Batch: 1, Id: 2 
Batch: 2, Id: 3 
Batch: 2, Id: 4 
Batch: 3, Id: 5 
Batch: 3, Id: 6 
Batch: 4, Id: 7 
Batch: 4, Id: 8 
Batch: 5, Id: 9 
Batch: 5, Id: 0 

ids sono suddivisi in 5 pezzi con 2 elementi.

+0

Grazie per aver parlato di [ModeLinq] (https://morelinq.github.io/). È una bella biblioteca. –

2

Anche se molte soluzioni potrebbero funzionare, penso che non siano molto efficienti. Supponiamo che tu voglia solo i primi elementi dei primi pezzi. Quindi non si desidera eseguire un'iterazione su tutti gli elementi (zillion) nella sequenza.

Il seguente numero verrà enumerato al massimo due volte: una volta per il Take e una volta per il Skip. E non conta più di qualsiasi più elementi di quanto si intende utilizzare:

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource> 
    (this IEnumerable<TSource> source, int chunkSize) 
{ 
    while (source.Any())      // while there are elements left 
    { // still something to chunk: 
     yield return source.Take(chunkSize); // return a chunk of chunkSize 
     source = source.Skip(chunkSize);  // skip the returned chunk 
    } 
} 
Problemi correlati