2009-08-28 16 views
28

tengo una IEnumerable<string> que me gustaría que se dividan en grupos de tres, así que si mi entrada tenía 6 elementos que se pueden conseguir una IEnumerable<IEnumerable<string>> volvió con dos elementos cada uno de los cuales contiene un IEnumerable<string> que contiene el contenido de mi cadena.Cómo puedo dividir un IEnumerable <String> en grupos de IEnumerable <string>

Busco la manera de hacer esto con LINQ en lugar de un simple bucle for

Gracias

Respuesta

28
var result = sequence.Select((s, i) => new { Value = s, Index = i }) 
        .GroupBy(item => item.Index/3, item => item.Value); 

Tenga en cuenta que este devolverá un IEnumerable<IGrouping<int,string>> que será funcionalmente similar a lo que desea. Sin embargo, si estrictamente necesario que escriba como IEnumerable<IEnumerable<string>> (para pasar a un método que espera que en C# 3.0, que no es compatible con los genéricos varianza,) se debe utilizar Enumerable.Cast:

var result = sequence.Select((s, i) => new { Value = s, Index = i }) 
        .GroupBy(item => item.Index/3, item => item.Value) 
        .Cast<IEnumerable<string>>(); 
+0

Eso era unbelivably rápida, gracias –

+2

¿El GroupBy tiene que recorrer toda la secuencia antes de obtener ningún resultado, o todavía conseguir ejecución diferida aquí? –

+0

@Don Kirkby: Para LINQ to Objects, '.GroupBy' no enumera la secuencia. Enumera toda la secuencia tan pronto como se llama a '.GetEnumerator' (por ejemplo, cuando se usa en' foreach' o algo así). –

0

me ocurrió con un diferente enfoque. Utiliza un iterador while bien pero los resultados se guardan en la memoria como un LINQ regular hasta que sea necesario.
Aquí está el código.

public IEnumerable<IEnumerable<T>> Paginate<T>(this IEnumerable<T> source, int pageSize) 
{ 
    List<IEnumerable<T>> pages = new List<IEnumerable<T>>(); 
    int skipCount = 0; 

    while (skipCount * pageSize < source.Count) { 
     pages.Add(source.Skip(skipCount * pageSize).Take(pageSize)); 
     skipCount += 1; 
    } 

    return pages; 
} 
+1

Nunca antes vi 'Runtime.CompilerServices.Extension' antes, así que lo busqué y MSDN dice" En C#, no necesita usar este atributo; debe usar el modificador 'this' para el primer parámetro para crear un método de extensión. En otras palabras, aunque es funcionalmente equivalente, se prefiere utilizar 'public IEnumerable > Paginate (esta fuente IEnumerable , int pageSize)' en lugar del atributo. – Davy8

+0

@ Davy8: recién estaba aprendiendo C# cuando envié esta respuesta, por lo que el código es un puerto directo de VB.NET. Ahora obtuve algo de dominio sobre C# y sé que este código no es realmente "correcto". He actualizado la respuesta ahora. –

+0

¿cómo obtuviste 'source.Count'? es un ienumerable, y al hacer 'Count()' en él enumera la colección una vez. – nawfal

20

Sé que esto ya ha sido contestada, pero si usted planea tomar rebanadas de IEnumerables menudo, entonces te recomiendo hacer un método de extensión genérica como esto:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkSize) 
{ 
    return source.Where((x,i) => i % chunkSize == 0).Select((x,i) => source.Skip(i * chunkSize).Take(chunkSize)); 
} 

continuación, puede utilizar sequence.Split(3) a consigue lo que quieres

(puede ponerle otro nombre como 'slice', o 'chunk' si no le gusta que 'split' ya se haya definido para cadenas. 'Split' es justo lo que llamé mina).

+0

+1. Me gusta el hecho de que puede lograr el mismo resultado que yo con una línea de código. –

+0

Ha pasado un tiempo y he estado usando tu código (solución/respuesta/como lo llames) por un tiempo y funciona perfectamente. Recientemente traté de analizar tu código y no pude entender la parte '.Where ((x, i) => i% chunkSize == 0)' de tu código, todavía funciona bien. Si no te importa, ¿podrías explicarme cómo funciona tu código? Gracias. –

+1

@ Alex ¡Ciertamente! Supongamos que su colección tiene 9 elementos y desea dividirla en grupos de 3. Lo único que realmente hace esa expresión es calcular cuántos grupos se van a realizar. Como puede ver, solo estoy realmente interesado en los índices en 'Dónde' y 'Seleccionar'. Voy de tener índices '0-8' en 'Dónde' a tener '0-2' en 'Seleccionar' ya que la cláusula 'Dónde 'solo devolverá 3 de los 9 elementos (verifique el resultado de' Enumerable.Range ' (0,9) .Seleccione ((x, i) => i% 3) 'para prueba!). Así que primero omito 0 (0 * 3) y tomo 3, luego omito 3 (1 * 3) y tomo 3, luego salteo 6 (2 * 3) y tomo 3! – diceguyd30

15

Inspirado por la implementación de @ dicegiuy30, quería crear una versión que solo itere sobre la fuente una vez y no construya todo el conjunto de resultados en la memoria para compensar. Lo mejor que se me ocurre es esto:

public static IEnumerable<IEnumerable<T>> Split2<T>(this IEnumerable<T> source, int chunkSize) { 
    var chunk = new List<T>(chunkSize); 
    foreach(var x in source) { 
     chunk.Add(x); 
     if(chunk.Count <= chunkSize) { 
      continue; 
     } 
     yield return chunk; 
     chunk = new List<T>(chunkSize); 
    } 
    if(chunk.Any()) { 
     yield return chunk; 
    } 
} 

De esta manera construyo cada porción a pedido. También me gustaría evitar el List<T> y simplemente transmitir ese contenido también, pero aún no lo he descifrado.

+1

+1 una gran implementación. Exactamente como lo hizo Jon Skeet: http://code.google.com/p/morelinq/source/browse/trunk/MoreLinq/Batch.cs – diceguyd30

+2

+1 esto parece ser muy eficiente, pero creo que tiene un error en la siguiente línea: 'if (trozo.Count <= trozoTamaño)' La línea correcta es la siguiente: 'if (trozo.Count

2

usando Microsoft.Reactive puedes hacer esto bastante simple e iterarás solo una vez a través de la fuente.

IEnumerable<string> source = new List<string>{"1", "2", "3", "4", "5", "6"}; 

IEnumerable<IEnumerable<string>> splited = source.ToObservable().Buffer(3).ToEnumerable(); 
+1

No necesita usar ToObservable y luego volver a ToEnumerable , puede usar el método de la Memoria intermedia de extensiones interactivas que funciona con Enumerable.Busca Ix-Main en nuget. –

+0

Si usa 'Reactive' NECESITA ToObservable. De lo contrario, debe usar 'Interactivo' – Emaborsa

2

Podemos mejorar la solución @ Afshari para hacer una verdadera evaluación perezosa. Utilizamos un método GroupAdjacentBy que produce grupos de elementos consecutivos con la misma clave:

sequence 
.Select((x, i) => new { Value = x, Index = i }) 
.GroupAdjacentBy(x=>x.Index/3) 
.Select(g=>g.Select(x=>x.Value)) 

Debido a que los grupos se produjeron uno por uno, esta solución funciona de manera eficiente con secuencias largas o infinitas.

27

Ésta es una respuesta tardía a este tema, pero aquí es un método que no utiliza ningún tipo de almacenamiento temporal:

public static class EnumerableExt 
{ 
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> input, int blockSize) 
    { 
     var enumerator = input.GetEnumerator(); 

     while (enumerator.MoveNext()) 
     { 
      yield return nextPartition(enumerator, blockSize); 
     } 
    } 

    private static IEnumerable<T> nextPartition<T>(IEnumerator<T> enumerator, int blockSize) 
    { 
     do 
     { 
      yield return enumerator.Current; 
     } 
     while (--blockSize > 0 && enumerator.MoveNext()); 
    } 
} 

Y algunos código de prueba:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var someNumbers = Enumerable.Range(0, 10000); 

     foreach (var block in someNumbers.Partition(100)) 
     { 
      Console.WriteLine("\nStart of block."); 

      foreach (int number in block) 
      { 
       Console.Write(number); 
       Console.Write(" "); 
      } 
     } 

     Console.WriteLine("\nDone."); 
     Console.ReadLine(); 
    } 
} 
+0

+1 para preservar la enumerabilidad del original. Es la solución que estaba buscando para trabajar con 'BlockingCollection'. –

+0

usted se merece +2 para incluir el código de prueba. no es tan conciso como algunos otros ejemplos, pero es más preciso en términos de mantener ienumerables y limitar iteraciones sobre la colección. ¡bien hecho! – mtazva

+3

NOTA: ¡Es fundamental comprender que este código NO es seguro para subprocesos! Tendría que transformar los ienumerables resultantes en un tipo concreto, sincrónicamente, antes de pasar los resultados al código asíncrono para garantizar que los artículos se recogen adecuadamente en lotes. – mtazva

0

respuesta de Mehrdad Afshari es excelente. Aquí es el un método de extensión que lo encapsula:

using System.Collections.Generic; 
using System.Linq; 

public static class EnumerableExtensions 
{ 
    public static IEnumerable<IEnumerable<T>> GroupsOf<T>(this IEnumerable<T> enumerable, int size) 
    { 
     return enumerable.Select((v, i) => new {v, i}).GroupBy(x => x.i/size, x => x.v); 
    } 
} 
Cuestiones relacionadas