2010-10-08 26 views
10

Estoy tratando de dividir una colección en varias colecciones mientras mantengo un ordenamiento que tengo en la colección. He intentado usar el siguiente método de extensión, pero los rompe incorrectamente. Básicamente, si tuviera que mirar los artículos en la colección, el orden debería ser el mismo en comparación con las colecciones rotas unidas. Aquí está el código que estoy usando eso no funciona:División de la colección C# en partes iguales, manteniendo el orden

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts) 
     { 
      int i = 0; 
      var splits = from name in list 
         group name by i++ % parts into part 
         select part.AsEnumerable(); 
      return splits; 
     } 
  • partes int = número de sub enumerables
+0

posible duplicado de la [Lista de reparto LINQ en listas de 8 miembros.] (Http://stackoverflow.com/questions/3773403/linq-partition-list-into-lists- of-8-members) –

+1

@Kirk Woll: No es lo mismo, en la pregunta que usted dio el método de extensión toma el número máximo de elementos en un sub enumerable mientras que aquí, según tengo entendido, hemos deseado el número de subenumerables. –

+0

@Andrew, tiene razón, veo que es el punto –

Respuesta

10

Tuve que hacer uso de esto para comparar una lista de objetos entre sí en grupos de 4 ... mantendrá los objetos en el orden que el original poseía. Podría ampliarse para hacer algo más que 'Lista'

/// <summary> 
/// Partition a list of elements into a smaller group of elements 
/// </summary> 
/// <typeparam name="T"></typeparam> 
/// <param name="list"></param> 
/// <param name="totalPartitions"></param> 
/// <returns></returns> 
public static List<T>[] Partition<T>(List<T> list, int totalPartitions) 
{ 
    if (list == null) 
     throw new ArgumentNullException("list"); 

    if (totalPartitions < 1) 
     throw new ArgumentOutOfRangeException("totalPartitions"); 

    List<T>[] partitions = new List<T>[totalPartitions]; 

    int maxSize = (int)Math.Ceiling(list.Count/(double)totalPartitions); 
    int k = 0; 

    for (int i = 0; i < partitions.Length; i++) 
    { 
     partitions[i] = new List<T>(); 
     for (int j = k; j < k + maxSize; j++) 
     { 
      if (j >= list.Count) 
       break; 
      partitions[i].Add(list[j]); 
     } 
     k += maxSize; 
    } 

    return partitions; 
} 
+0

Gracias. Funcionó muy bien. –

+0

¿No hay un error con este algoritmo? ¿Alguien ha intentado verificarlo? Me parece que la línea int maxSize = (int) Math.Ceiling (list.Count/(double) totalPartitions) podría generar recuentos máximos no válidos. Por ejemplo; una lista de 21 elementos para dividir en decir 10 grupos deseados; en realidad solo creará 7 grupos de 3 elementos con 3 grupos vacíos. La razón es que el cálculo del tamaño se redondeará de 2.1 a 3 –

+0

Sí, me imagino que sí. Si tengo una lista del alfabeto y puse para dividir eso en 100 particiones, obtendré un objeto que tiene los primeros 0-25 llenos con 1 ítem cada uno y 26-99 estará vacío. LINQ a partir de 3.5 probablemente corrija el problema, simplemente no he visto esto en unos pocos años. Hizo lo que necesitaba que hiciera, y obviamente lo que necesitaba el OP. –

0

Según entiendo desea romper enumerable en varias partes con igual tamaño y sin romper el orden de tus elementos. Parece que la única opción es hacer que la longitud de su entrada sea enumerable primero, por lo que necesitaría al menos dos iteraciones a través del enumerable.

+0

@Kirk Woll: depende de lo que se necesite. Si "partes" es la cantidad de elementos en un enumerable que es fácil de resolver. Pero el código anterior me hace pensar que "partes" es el número de subenumerables deseados, no elementos en los enumerables. –

+0

"parts" es el número de subenuncios –

1
double partLength = list.Count()/(double)parts; 

    int i = 0; 
    var splits = from name in list 
       group name by Math.Floor((double)(i++/partLength)) into part 
       select part; 
+0

En su ejemplo, "splits" se convierte en IEnumerable > pero debe ser un IEnumerable > –

+0

Corrígeme si estoy equivocado, pero no está IGrouping an IEnumerable ? – kevev22

2

biblioteca MoreLINQ de Jon Skeet puede hacer el truco para usted:

https://code.google.com/p/morelinq/source/browse/MoreLinq/Batch.cs

var items = list.Batch(parts); // gives you IEnumerable<IEnumerable<T>> 
var items = list.Batch(parts, seq => seq.ToList()); // gives you IEnumerable<List<T>> 
// etc... 

Otro ejemplo:

public class Program 
{ 
    static void Main(string[] args) 
    { 
     var list = new List<int>(); 
     for (int i = 1; i < 10000; i++) 
     { 
      list.Add(i); 
     } 

     var batched = list.Batch(681); 

     // will print 15. The 15th element has 465 items... 
     Console.WriteLine(batched.Count().ToString()); 
     Console.WriteLine(batched.ElementAt(14).Count().ToString()); 
     Console.WriteLine(); 
     Console.WriteLine("Press enter to exit."); 
     Console.ReadLine(); 
    } 
} 

Cuando he escaneado el contenido de los lotes, el el orden fue preservado.

+0

En estos ejemplos, parece querer que "partes" sea la cantidad de elementos en cada colección. En MI ejemplo, quiero que "partes" sea la cantidad de sub colecciones que se crearán. –

0
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts) 
    { 
     int nGroups = (int)Math.Ceiling(list.Count()/(double)parts); 

     var groups = Enumerable.Range(0, nGroups); 

     return groups.Select(g => list.Skip(g * parts).Take(parts)); 
    } 
+0

Esto no es correcto.Esto creará grupos con un tamaño igual a las partes. –

0

Esto hará exactamente lo solicitado. También servirá para grupos desiguales, es decir27 elementos en 10 grupos rendirán 7 grupos de tres y 3 grupos de dos

 public static IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(this IEnumerable<T> list, int parts) 
    { 
     if (list.Count() == 0) return Enumerable.Empty<IEnumerable<T>>(); 

     var toreturn = new List<IEnumerable<T>>(); 

     var splitFactor = Decimal.Divide((decimal)list.Count(), parts); 
     int currentIndex = 0; 

     for (var i = 0; i < parts; i++) 
     { 
      var toTake = Convert.ToInt32(
       i == 0 ? Math.Ceiling(splitFactor) : (
        (Decimal.Compare(Decimal.Divide(Convert.ToDecimal(currentIndex), Convert.ToDecimal(i)), splitFactor) > 0) ? 
         Math.Floor(splitFactor) : Math.Ceiling(splitFactor))); 

      toreturn.Add(list.Skip(currentIndex).Take(toTake)); 
      currentIndex += toTake; 
     } 

     return toreturn; 
    } 

Para fines de demostración

 [TestMethod] 
    public void splitlist() 
    { 
     var list = new decimal[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 }; 

     var splitlists = list.SplitMaintainingOrder(10); 

     int i = 1; 

     foreach (var group in splitlists) 
     { 
      Console.WriteLine("Group {0} elements {1}", i++, String.Join(",", group));     
     } 
    } 

los encima de los rendimientos de demostración

Test Name: splitlist 
Test Outcome: Passed 
Result StandardOutput: 
Group 1 elements 1,2,3 
Group 2 elements 4,5 
Group 3 elements 6,7,8 
Group 4 elements 9,10,11 
Group 5 elements 12,13 
Group 6 elements 14,15,16 
Group 7 elements 17,18,19 
Group 8 elements 20,21 
Group 9 elements 22,23,24 
Group 10 elements 25,26,27 
4

A LINQ ligeramente más limpio enfoque, para esta pregunta bastante antigua:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int n) 
{ 
    var count = source.Count(); 

    return source.Select((x, i) => new { value = x, index = i }) 
     .GroupBy(x => x.index/(int)Math.Ceiling(count/(double)n)) 
     .Select(x => x.Select(z => z.value)); 
} 
0

Para dividir una lista genérica en la igualdad de trozos utilizan a continuación método genérico

private IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(IEnumerable<T> list, int columnCount) 
 
       { 
 
        var elementsCount = list.Count(); 
 
        int rowCount = elementsCount/columnCount; 
 
        int noOfCells = elementsCount % columnCount; 
 

 
        int finalRowCount = rowCount; 
 
        if (noOfCells > 0) 
 
        { 
 
         finalRowCount++; 
 
        } 
 

 
        var toreturn = new List<IEnumerable<T>>(); 
 
        var pushto = 0; 
 
        for (int j = 0; j < columnCount; j++) 
 
        { 
 
         var start = j; 
 
         int i = 0; 
 
         var end = i; 
 
         for (i = 0; i < finalRowCount; i++) 
 
         { 
 
          if ((i < rowCount) || ((i == rowCount) && (j < noOfCells))) 
 
          { 
 
           start = j; 
 
           end = i; 
 
          } 
 
         } 
 
         toreturn.Add(list.Skip(pushto).Take(end + 1)); 
 
         pushto += end + 1; 
 
        } 
 

 
        return toreturn; 
 

 
       }

List<int> recordNumbers = new List<int>() { 1, 2, 3, 4, 5, 6,7,8,9,10,11}; 
 

 
var splitedItems = SplitMaintainingOrder<int>(recordNumbers , 4);

Output will be: 
 

 
List 1 : 1,2,3 
 
List 2 : 4,5,6 
 
List 3 : 7,8,9 
 
List 4 : 10,11

~ feliz de codificación ..

Cuestiones relacionadas