c# - elementos - Dividir la lista en sublistas con LINQ
comparar elementos de dos listas c# (26)
¿Qué hay de este?
var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3
var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
.Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
.ToList();
Por lo que sé, GetRange() es lineal en términos de cantidad de elementos tomados. Así que esto debería funcionar bien.
¿Hay alguna manera de que pueda separar una List<SomeObject>
en varias listas separadas de SomeObject
, usando el índice del elemento como el delimitador de cada división?
Déjame ejemplificar:
Tengo una List<SomeObject>
y necesito una List<List<SomeObject>>
o List<SomeObject>[]
, para que cada una de estas listas resultantes contenga un grupo de 3 elementos de la lista original (secuencialmente).
p.ej.:
Lista original:
[a, g, e, w, p, s, q, f, x, y, i, m, c]
Listas resultantes:
[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]
También necesitaría que el tamaño de las listas resultantes sea un parámetro de esta función.
Aquí hay una rutina de división de listas que escribí hace un par de meses:
public static List<List<T>> Chunk<T>(
List<T> theList,
int chunkSize
)
{
List<List<T>> result = theList
.Select((x, i) => new {
data = x,
indexgroup = i / chunkSize
})
.GroupBy(x => x.indexgroup, x => x.data)
.Select(g => new List<T>(g))
.ToList();
return result;
}
Código antiguo, pero esto es lo que he estado usando:
public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
{
var toReturn = new List<T>(max);
foreach (var item in source)
{
toReturn.Add(item);
if (toReturn.Count == max)
{
yield return toReturn;
toReturn = new List<T>(max);
}
}
if (toReturn.Any())
{
yield return toReturn;
}
}
Completamente perezoso, sin contar ni copiar:
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
{
if (len == 0)
throw new ArgumentNullException();
var enumer = source.GetEnumerator();
while (enumer.MoveNext())
{
yield return Take(enumer.Current, enumer, len);
}
}
private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
{
while (true)
{
yield return head;
if (--len == 0)
break;
if (tail.MoveNext())
head = tail.Current;
else
break;
}
}
}
Creo que la siguiente sugerencia sería la más rápida. Estoy sacrificando la pereza de la fuente Enumerable por la capacidad de usar Array.Copy y sabiendo de antemano la duración de cada una de mis sublistas.
public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
T[] array = items as T[] ?? items.ToArray();
for (int i = 0; i < array.Length; i+=size)
{
T[] chunk = new T[Math.Min(size, array.Length - i)];
Array.Copy(array, i, chunk, 0, chunk.Length);
yield return chunk;
}
}
En general, el enfoque sugerido por CaseyB funciona bien, de hecho, si está pasando una List<T>
es difícil criticarlo, tal vez lo cambie a:
public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
var pos = 0;
while (source.Skip(pos).Any())
{
yield return source.Skip(pos).Take(chunksize);
pos += chunksize;
}
}
Lo que evitará cadenas de llamadas masivas. No obstante, este enfoque tiene un defecto general. Se materializan dos enumeraciones por porción, para resaltar el problema, intente ejecutar:
foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
Console.WriteLine(item);
}
// wait forever
Para superar esto, podemos probar Cameron''s enfoque Cameron''s , que pasa la prueba anterior con gran éxito, ya que solo recorre la enumeración una vez.
El problema es que tiene un defecto diferente, materializa cada elemento en cada fragmento, el problema con este enfoque es que te quedas sin memoria.
Para ilustrar que intente ejecutar:
foreach (var item in Enumerable.Range(1, int.MaxValue)
.Select(x => x + new string(''x'', 100000))
.Clump(10000).Skip(100).First())
{
Console.Write(''.'');
}
// OutOfMemoryException
Finalmente, cualquier implementación debería poder manejar la iteración fuera de orden de los fragmentos, por ejemplo:
Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]
Muchas soluciones altamente óptimas como mi primera revision de esta respuesta fallaron allí. El mismo problema se puede ver en la respuesta optimizada de casperOne .
Para resolver todos estos problemas, puede utilizar lo siguiente:
namespace ChunkedEnumerator
{
public static class Extensions
{
class ChunkedEnumerable<T> : IEnumerable<T>
{
class ChildEnumerator : IEnumerator<T>
{
ChunkedEnumerable<T> parent;
int position;
bool done = false;
T current;
public ChildEnumerator(ChunkedEnumerable<T> parent)
{
this.parent = parent;
position = -1;
parent.wrapper.AddRef();
}
public T Current
{
get
{
if (position == -1 || done)
{
throw new InvalidOperationException();
}
return current;
}
}
public void Dispose()
{
if (!done)
{
done = true;
parent.wrapper.RemoveRef();
}
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
position++;
if (position + 1 > parent.chunkSize)
{
done = true;
}
if (!done)
{
done = !parent.wrapper.Get(position + parent.start, out current);
}
return !done;
}
public void Reset()
{
// per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
throw new NotSupportedException();
}
}
EnumeratorWrapper<T> wrapper;
int chunkSize;
int start;
public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
{
this.wrapper = wrapper;
this.chunkSize = chunkSize;
this.start = start;
}
public IEnumerator<T> GetEnumerator()
{
return new ChildEnumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class EnumeratorWrapper<T>
{
public EnumeratorWrapper (IEnumerable<T> source)
{
SourceEumerable = source;
}
IEnumerable<T> SourceEumerable {get; set;}
Enumeration currentEnumeration;
class Enumeration
{
public IEnumerator<T> Source { get; set; }
public int Position { get; set; }
public bool AtEnd { get; set; }
}
public bool Get(int pos, out T item)
{
if (currentEnumeration != null && currentEnumeration.Position > pos)
{
currentEnumeration.Source.Dispose();
currentEnumeration = null;
}
if (currentEnumeration == null)
{
currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
}
item = default(T);
if (currentEnumeration.AtEnd)
{
return false;
}
while(currentEnumeration.Position < pos)
{
currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
currentEnumeration.Position++;
if (currentEnumeration.AtEnd)
{
return false;
}
}
item = currentEnumeration.Source.Current;
return true;
}
int refs = 0;
// needed for dispose semantics
public void AddRef()
{
refs++;
}
public void RemoveRef()
{
refs--;
if (refs == 0 && currentEnumeration != null)
{
var copy = currentEnumeration;
currentEnumeration = null;
copy.Source.Dispose();
}
}
}
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
if (chunksize < 1) throw new InvalidOperationException();
var wrapper = new EnumeratorWrapper<T>(source);
int currentPos = 0;
T ignore;
try
{
wrapper.AddRef();
while (wrapper.Get(currentPos, out ignore))
{
yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
currentPos += chunksize;
}
}
finally
{
wrapper.RemoveRef();
}
}
}
class Program
{
static void Main(string[] args)
{
int i = 10;
foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
{
foreach (var n in group)
{
Console.Write(n);
Console.Write(" ");
}
Console.WriteLine();
if (i-- == 0) break;
}
var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();
foreach (var idx in new [] {3,2,1})
{
Console.Write("idx " + idx + " ");
foreach (var n in stuffs[idx])
{
Console.Write(n);
Console.Write(" ");
}
Console.WriteLine();
}
/*
10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
*/
Console.ReadKey();
}
}
}
También hay una serie de optimizaciones que podría introducir para la iteración fuera de orden de los fragmentos, que está fuera del alcance aquí.
En cuanto a qué método debe elegir? Depende totalmente del problema que estés tratando de resolver. Si no está preocupado por la primera falla, la respuesta simple es increíblemente atractiva.
Tenga en cuenta que, como con la mayoría de los métodos, esto no es seguro para los subprocesos múltiples, las cosas pueden volverse extrañas si desea que sea seguro para el subproceso, tendría que corregir el EnumeratorWrapper
.
Encontramos que la solución de David B funcionó mejor. Pero lo adaptamos a una solución más general:
list.GroupBy(item => item.SomeProperty)
.Select(group => new List<T>(group))
.ToArray();
Es una solución antigua pero tenía un enfoque diferente. Utilizo Skip
para desplazarme al desplazamiento deseado y Take
para extraer el número deseado de elementos:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
if (chunkSize <= 0)
throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");
var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);
return Enumerable.Range(0, nbChunks)
.Select(chunkNb => source.Skip(chunkNb*chunkSize)
.Take(chunkSize));
}
Escribí un método de extensión Clump hace varios años. Funciona muy bien, y es la implementación más rápida aquí. :PAG
/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
if (source == null)
throw new ArgumentNullException("source");
if (size < 1)
throw new ArgumentOutOfRangeException("size", "size must be greater than 0");
return ClumpIterator<T>(source, size);
}
private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
Debug.Assert(source != null, "source is null.");
T[] items = new T[size];
int count = 0;
foreach (var item in source)
{
items[count] = item;
count++;
if (count == size)
{
yield return items;
items = new T[size];
count = 0;
}
}
if (count > 0)
{
if (count == size)
yield return items;
else
{
T[] tempItems = new T[count];
Array.Copy(items, tempItems, count);
yield return tempItems;
}
}
}
Esta es una vieja pregunta pero esto es con lo que terminé; Enumera el enumerable solo una vez, pero crea listas para cada una de las particiones. No sufre de comportamiento inesperado cuando se llama a ToArray()
como lo hacen algunas de las implementaciones:
public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (chunkSize < 1)
{
throw new ArgumentException("Invalid chunkSize: " + chunkSize);
}
using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
{
IList<T> currentChunk = new List<T>();
while (sourceEnumerator.MoveNext())
{
currentChunk.Add(sourceEnumerator.Current);
if (currentChunk.Count == chunkSize)
{
yield return currentChunk;
currentChunk = new List<T>();
}
}
if (currentChunk.Any())
{
yield return currentChunk;
}
}
}
Esta pregunta es un poco antigua, pero acabo de escribir esto, y creo que es un poco más elegante que las otras soluciones propuestas:
/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
while (source.Any())
{
yield return source.Take(chunksize);
source = source.Skip(chunksize);
}
}
La siguiente solución es la más compacta que se me ocurre y es O (n).
public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
var list = source as IList<T> ?? source.ToList();
for (int start = 0; start < list.Count; start += chunksize)
{
T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
for (int i = 0; i < chunk.Length; i++)
chunk[i] = list[start + i];
yield return chunk;
}
}
Me parece que este pequeño fragmento hace el trabajo bastante bien.
public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
var offset = 0;
while (offset < source.Count)
{
yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
offset += chunkSize;
}
}
Ok, aquí está mi opinión sobre esto:
- Completamente perezoso: funciona en infinitos enumerados
- sin copia intermedia / almacenamiento en búfer
- O (n) tiempo de ejecución
- Funciona también cuando las secuencias internas son solo parcialmente consumidas.
public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
int chunkSize)
{
if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");
using (var e = enumerable.GetEnumerator())
while (e.MoveNext())
{
var remaining = chunkSize; // elements remaining in the current chunk
var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());
yield return e.GetChunk(innerMoveNext);
while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
}
}
private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
Func<bool> innerMoveNext)
{
do yield return e.Current;
while (innerMoveNext());
}
Ejemplo de uso
var src = new [] {1, 2, 3, 4, 5, 6};
var c3 = src.Chunks(3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.Chunks(4); // {{1, 2, 3, 4}, {5, 6}};
var sum = c3.Select(c => c.Sum()); // {6, 15}
var count = c3.Count(); // 2
var take2 = c3.Select(c => c.Take(2)); // {{1, 2}, {4, 5}}
Explicaciones
El código funciona anidando dos iteradores basados en el yield
.
El iterador externo debe realizar un seguimiento de la cantidad de elementos que ha consumido efectivamente el iterador interno (chunk). Esto se hace cerrando sobre el remaining
con innerMoveNext()
. Los elementos no consumidos de un fragmento se descartan antes de que el iterador externo produzca el siguiente fragmento. Esto es necesario porque de lo contrario obtendrás resultados inconsistentes, cuando los enumerables internos no se consuman (completamente) (por ejemplo, c3.Count()
devolvería 6).
Nota: la respuesta se ha actualizado para solucionar las deficiencias señaladas por @aolszowka.
Podemos mejorar la solución de @JaredPar para hacer una verdadera evaluación perezosa. Usamos 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 producen uno por uno, esta solución funciona de manera eficiente con secuencias largas o infinitas.
Prueba el siguiente código.
public static IList<IList<T>> Split<T>(IList<T> source)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 3)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
La idea es agrupar primero los elementos por índices. Dividir por tres tiene el efecto de agruparlos en grupos de 3. Luego convierta cada grupo en una lista y el IEnumerable
de la List
en una List
de la List
s
Si la lista es de tipo system.collections.generic, puede usar el método "CopyTo" disponible para copiar elementos de su matriz a otras subarreglas. Usted especifica el elemento de inicio y el número de elementos para copiar.
También puede hacer 3 clones de su lista original y usar el "Eliminar Rango" en cada lista para reducir la lista al tamaño que desee.
O simplemente crea un método de ayuda para hacerlo por ti.
Solo poniendo en mis dos centavos. Si desea "agrupar" la lista (visualizar de izquierda a derecha), puede hacer lo siguiente:
public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
{
List<List<T>> result = new List<List<T>>();
for (int i = 0; i < numberOfBuckets; i++)
{
result.Add(new List<T>());
}
int count = 0;
while (count < source.Count())
{
var mod = count % numberOfBuckets;
result[mod].Add(source[count]);
count++;
}
return result;
}
Usando particionamiento modular:
public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}
System.Interactive proporciona Buffer()
para este propósito. Algunas pruebas rápidas muestran que el rendimiento es similar a la solución de Sam.
Podría usar una serie de consultas que usan Take
y Skip
, pero eso agregaría demasiadas iteraciones en la lista original, creo.
Más bien, creo que deberías crear tu propio iterador, así:
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
Luego puede llamar a esto y está habilitado para LINQ así que puede realizar otras operaciones en las secuencias resultantes.
A la luz de la respuesta de Sam , sentí que había una manera más fácil de hacer esto sin:
- Iterando a través de la lista de nuevo (lo que no hice originalmente)
- Materializar los elementos en grupos antes de liberar el trozo (para grandes trozos de elementos, habría problemas de memoria)
- Todo el código que Sam publicó
Dicho esto, aquí hay otro pase, que he codificado en un método de extensión a IEnumerable<T>
llamado Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
No hay nada sorprendente allí, solo la comprobación básica de errores.
Pasando a ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there''s nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
Básicamente, obtiene el IEnumerator<T>
e itera manualmente a través de cada elemento. Comprueba si hay algún elemento actualmente para ser enumerado. Una vez que se ha enumerado cada fragmento, si no quedan elementos, se rompe.
Una vez que detecta que hay elementos en la secuencia, delega la responsabilidad de la IEnumerable<T>
interna de IEnumerable<T>
a ChunkSequence
:
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
Ya que MoveNext
ya fue llamado en IEnumerator<T>
pasado a ChunkSequence
, produce el elemento devuelto por Current
y luego incrementa el conteo, asegurándose de nunca devolver más que elementos de chunkSize
y pasar al siguiente elemento en la secuencia después de cada iteración ( pero cortocircuitado si el número de elementos producidos excede el tamaño del fragmento).
Si no quedan elementos, entonces el método InternalChunk
realizará otra pasada en el bucle externo, pero cuando se llame a MoveNext
una segunda vez, seguirá devolviendo el valor falso, MoveNext (el énfasis es mío):
Si MoveNext pasa el final de la colección, el enumerador se coloca después del último elemento de la colección y MoveNext devuelve false. Cuando el enumerador está en esta posición, las llamadas posteriores a MoveNext también devuelven false hasta que se llama a Reset.
En este punto, el bucle se romperá, y la secuencia de secuencias terminará.
Esta es una prueba simple:
static void Main()
{
string s = "agewpsqfxyimc";
int count = 0;
// Group by three.
foreach (IEnumerable<char> g in s.Chunk(3))
{
// Print out the group.
Console.Write("Group: {0} - ", ++count);
// Print the items.
foreach (char c in g)
{
// Print the item.
Console.Write(c + ", ");
}
// Finish the line.
Console.WriteLine();
}
}
Salida:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Una nota importante, esto no funcionará si no borra toda la secuencia secundaria o se interrumpe en cualquier punto de la secuencia principal. Esta es una advertencia importante, pero si su caso de uso es que consumirá cada elemento de la secuencia de secuencias, esto funcionará para usted.
Además, hará cosas extrañas si juegas con la orden, tal como lo hizo Sam en un momento .
Para insertar mis dos centavos ...
Al usar el tipo de lista para la fuente a ser fragmentada, encontré otra solución muy compacta:
public static IEnumerable<IEnumerable<TSource>> Chunk<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
// copy the source into a list
var chunkList = source.ToList();
// return chunks of ''chunkSize'' items
while (chunkList.Count > chunkSize)
{
yield return chunkList.GetRange(0, chunkSize);
chunkList.RemoveRange(0, chunkSize);
}
// return the rest
yield return chunkList;
}
Puede trabajar con generadores infinitos:
a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
.Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
.Where((x, i) => i % 3 == 0)
Código de demostración: https://ideone.com/GKmL7M
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
private static void DoIt(IEnumerable<int> a)
{
Console.WriteLine(String.Join(" ", a));
foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
Console.WriteLine(String.Join(" ", x));
Console.WriteLine();
}
public static void Main()
{
DoIt(new int[] {1});
DoIt(new int[] {1, 2});
DoIt(new int[] {1, 2, 3});
DoIt(new int[] {1, 2, 3, 4});
DoIt(new int[] {1, 2, 3, 4, 5});
DoIt(new int[] {1, 2, 3, 4, 5, 6});
}
}
1
1 2
1 2 3
1 2 3
1 2 3 4
1 2 3
1 2 3 4 5
1 2 3
1 2 3 4 5 6
1 2 3
4 5 6
Pero en realidad preferiría escribir el método correspondiente sin linq.
Para cualquier persona interesada en una solución empaquetada / mantenida, la biblioteca MoreLINQ proporciona el Batch
método de extensión que coincide con su comportamiento solicitado:
IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);
La Batch
implementación es similar a la respuesta de Cameron MacFarland , con la adición de una sobrecarga para transformar el trozo / lote antes de regresar, y funciona bastante bien.
Tan performático como el enfoque de Sam Saffron .
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");
return BatchImpl(source, size).TakeWhile(x => x.Any());
}
static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
var values = new List<T>();
var group = 1;
var disposed = false;
var e = source.GetEnumerator();
try
{
while (!disposed)
{
yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
group++;
}
}
finally
{
if (!disposed)
e.Dispose();
}
}
static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
var min = (group - 1) * size + 1;
var max = group * size;
var hasValue = false;
while (values.Count < min && e.MoveNext())
{
values.Add(e.Current);
}
for (var i = min; i <= max; i++)
{
if (i <= values.Count)
{
hasValue = true;
}
else if (hasValue = e.MoveNext())
{
values.Add(e.Current);
}
else
{
dispose();
}
if (hasValue)
yield return values[i - 1];
else
yield break;
}
}
}
Tomé la respuesta principal y la convertí en un contenedor de IOC para determinar dónde dividir. (¿ Quién realmente está buscando dividir solo en 3 elementos, al leer esta publicación mientras busca una respuesta? )
Este método permite dividir en cualquier tipo de artículo según sea necesario.
public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
int groupIndex = 0;
return main.Select( item => new
{
Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex),
Value = item
})
.GroupBy( it2 => it2.Group)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
Así que para el OP el código sería
var it = new List<string>()
{ "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
int index = 0;
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );