2012-07-13 21 views
96

Estoy intentando dividir una lista en una serie de listas más pequeñas.Dividir una lista en listas más pequeñas de N tamaño

Mi problema: Mi función para dividir listas no las divide en listas del tamaño correcto. ¿Debería dividirlos en listas de tamaño 30 pero en su lugar las divide en listas de tamaño 114?

¿Cómo puedo hacer que mi función divida una lista en X número de Listas de tamaño 30 o menos?

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

Si utilizo la función en una lista de tamaño 144, entonces la salida es:

Índice: 4, tamaño: 120
Índice: 3, Tamaño: Índice 114
: 2 , Tamaño: 114
Índice: 1, Tamaño: 114
Índice: 0, Tamaño: 114

+1

Si una solución LINQ es aceptable, [esta pregunta puede ser de alguna ayuda] (http://stackoverflow.com/questions/419019/split -list-into-sublists-with-linq). –

+0

Específicamente la respuesta de Sam Saffron sobre la pregunta anterior. Y a menos que esto sea para una tarea escolar, solo usaría su código y me detendría. – jcolebrand

Respuesta

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

versión genérica:

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

Así que si tengo una lista de trillones de longitud, y quiero dividirme en listas más pequeñas de longitud 30, y de cada lista más pequeña solo quiero tomar (1), entonces sigo creando listas de 30 elementos de que tiro 29 artículos. ¡Esto se puede hacer de manera más inteligente! –

24

¿qué tal:

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

¿Esto va a consumir mucha memoria? Cada vez que aparece locations.Skip.ToList, me pregunto si se asigna más memoria y si una lista nueva hace referencia a los elementos no filmados. – Zasz

+0

sí nueva lista se crea en cada ciclo. Sí, consume memoria. Pero si tiene problemas de memoria, este no es el lugar para optimizar, ya que las instancias de esas listas están listas para ser recogidas en el siguiente ciclo. Puede intercambiar el rendimiento de la memoria omitiendo 'ToList', pero no me molestaría en tratar de optimizarlo, es tan trivial y poco probable es un cuello de botella. El principal beneficio de esta implementación es su trivialidad, es fácil de entender. Si lo desea, puede usar la respuesta aceptada, no crea esas listas, pero es un poco más complejo. – Rafal

+0

'.Skip (n)' itera sobre elementos 'n' cada vez que se invoca, aunque esto puede estar bien, es importante tener en cuenta el código de rendimiento crítico. http://stackoverflow.com/questions/20002975/performance-of-skip-and-similar-functions-like-take – Chakrava

5

Tengo un método genérico que tomaría cualquier tipo incluyen flotador, y ha sido la unidad probada, creo que sirve:

/// <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

Gracias. ¿Se pregunta si podría actualizar los comentarios con la definición del parámetro maxCount? Una red de seguridad? –

222

Sugeriría usar este método de extensión para dividir la lista fuente en las sublistas por tamaño de fragmento especificado:

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

Por ejemplo, si tira la lista de 18 elementos por 5 elementos por porción, le da la lista de 4 sublistas con los siguientes elementos dentro: 5-5-5-3.

+7

awesome solution – MonsterMMORPG

+3

Antes de utilizar esto en producción, asegúrese de comprender cuáles son las implicaciones de tiempo de ejecución para la memoria y el rendimiento. El hecho de que LINQ pueda ser sucinto no significa que sea una buena idea. – Nick

+3

Definitivamente, @Nick, sugeriría, en general, pensar antes de hacer cualquier cosa. La fragmentación con LINQ no debe ser una operación frecuente que se repite miles de veces. Por lo general, necesita dividir listas para el procesamiento de elementos lote por lote y/o en paralelo. –

9

solución Serj-Tm está bien, también esta es la versión genérica como método de extensión para las listas (ponerlo en una clase estática):

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

puedo encontrar respuesta aceptada (Serj-Tm) más robusto, pero me gustaría sugerir una versión genérica.

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 tienen método llamado 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++; 
} 

resultado es

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 formas dividido en 5 trozos con 2 elementos.

+0

Gracias por contarnos acerca de [ModeLinq] (https://morelinq.github.io/). Es una buena biblioteca. –

2

Aunque la mayoría de las soluciones pueden funcionar, creo que no son muy eficientes. Supongamos que solo quiere los primeros elementos de los primeros trozos. Entonces no querrá iterar sobre todos los elementos (trillón) en su secuencia.

Lo siguiente será como máximo enumerar dos veces: una para el Take y otra para el Skip. No va a enumerar sobre cualquier elemento más que va a utilizar:

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 
    } 
} 
Cuestiones relacionadas