¿Hay alguna manera de separar un List<SomeObject>
en varias listas separadas SomeObject
, usando el índice del elemento como delimitador de cada división?
Déjame ejemplificar:
Tengo un List<SomeObject>
y necesito un List<List<SomeObject>>
o List<SomeObject>[]
, de modo que cada una de estas listas resultantes contendrá 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.
fuente
[a,g,e]
antes de enumerar más de la lista original.GroupBy(x=>f(x)).First()
nunca cederá un grupo. OP preguntó sobre listas, pero si escribimos para trabajar con IEnumerable, haciendo solo una iteración, cosechamos la ventaja de rendimiento.Esta pregunta es un poco vieja, pero acabo de escribirla y creo que es un poco más elegante que las otras soluciones propuestas:
fuente
if (chunksize <= 0) throw new ArgumentException("Chunk size must be greater than zero.", "chunksize");
O(n²)
. Puede recorrer la lista y obtener unO(n)
tiempo.source
se reemplaza por un envueltoIEnumerable
cada vez. Entonces, tomar elementos desource
atraviesa capas deSkip
sEn general, el enfoque sugerido por CaseyB funciona bien, de hecho, si lo está pasando
List<T>
es difícil criticarlo, tal vez lo cambiaría a:Lo que evitará cadenas de llamadas masivas. No obstante, este enfoque tiene un defecto general. Se materializan dos enumeraciones por fragmento, para resaltar el problema intente ejecutar:
Para superar esto, podemos probar Cameron's enfoque , 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 ese enfoque es que te queda poca memoria.
Para ilustrar eso intente ejecutar:
Finalmente, cualquier implementación debería ser capaz de manejar la iteración de fragmentos fuera de orden, por ejemplo:
Muchas soluciones altamente óptimas como mi primera revisión de esta respuesta fallaron allí. El mismo problema se puede ver en la respuesta optimizada de casperOne .
Para abordar todos estos problemas, puede utilizar lo siguiente:
También hay una ronda de optimizaciones que podría introducir para la iteración de fragmentos fuera de orden, que está fuera de alcance aquí.
¿En cuanto a qué método debes elegir? Depende totalmente del problema que intente resolver. Si no le preocupa 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 subprocesos múltiples, las cosas pueden volverse extrañas si desea que sea seguro para subprocesos que necesitaría modificar
EnumeratorWrapper
.fuente
Usted podría utilizar una serie de consultas que utilizan
Take
ySkip
, pero eso sería añadir demasiadas iteraciones en la lista original, creo.Más bien, creo que debería crear un iterador propio, así:
Luego puede llamar a esto y está habilitado para LINQ para que pueda 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:
Dicho esto, aquí hay otro pase, que he codificado en un método de extensión
IEnumerable<T>
llamadoChunk
:No hay nada sorprendente allí, solo una comprobación básica de errores.
Pasando a
ChunkInternal
:Básicamente, obtiene el
IEnumerator<T>
e itera manualmente a través de cada elemento. Comprueba si hay algún elemento actualmente para enumerar. Después de enumerar cada fragmento, si no queda ningún elemento, se desglosa.Una vez que detecta que hay elementos en la secuencia, delega la responsabilidad de la
IEnumerable<T>
implementación interna aChunkSequence
:Como
MoveNext
ya se llamó alIEnumerator<T>
pasadoChunkSequence
, produce el elemento devuelto porCurrent
y luego incrementa el conteo, asegurándose de no devolver nunca más quechunkSize
elementos y moviéndose al siguiente elemento en la secuencia después de cada iteración (pero en cortocircuito si el número de los artículos producidos exceden el tamaño del fragmento).Si no quedan elementos, entonces el
InternalChunk
método hará otra pasada en el bucle externo, pero cuandoMoveNext
se llama por segunda vez, aún devolverá falso, según la documentación (énfasis mío):En este punto, el bucle se romperá y la secuencia de secuencias terminará.
Esta es una prueba simple:
Salida:
Una nota importante, esto no funcionará si no drena toda la secuencia secundaria o se rompe 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 el orden, tal como lo hizo Sam en un momento .
fuente
List<T>
, obviamente tendrá problemas de memoria debido al almacenamiento en búfer. En retrospectiva, debería haberlo notado en la respuesta, pero parecía que en ese momento el foco estaba en demasiadas iteraciones. Dicho esto, su solución es realmente más peluda. No lo he probado, pero ahora me hace preguntarme si hay una solución menos peluda.Ok, aquí está mi opinión al respecto:
Ejemplo de uso
Explicaciones
El código funciona anidando dos
yield
iteradores basados.El iterador externo debe realizar un seguimiento de cuántos elementos ha consumido efectivamente el iterador interno (fragmento). Esto se hace cerrando
remaining
coninnerMoveNext()
. 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 se obtienen resultados inconsistentes, cuando los enumerables internos no se consumen (por completo) (por ejemploc3.Count()
, devolverían 6).fuente
completamente vago, sin contar ni copiar:
fuente
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.
fuente
Podemos mejorar la solución de @ JaredPar para hacer una verdadera evaluación perezosa. Utilizamos un
GroupAdjacentBy
método que produce grupos de elementos consecutivos con la misma clave:Debido a que los grupos se obtienen uno por uno, esta solución funciona eficientemente con secuencias largas o infinitas.
fuente
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í. :PAGS
fuente
System.Interactive proporciona
Buffer()
para este propósito. Algunas pruebas rápidas muestran que el rendimiento es similar a la solución de Sam.fuente
Buffer()
vuelveIEnumerable<IList<T>>
así que sí, probablemente tengas un problema allí, no se transmite como el tuyo.Aquí hay una rutina de división de listas que escribí hace un par de meses:
fuente
Creo que este pequeño fragmento hace el trabajo bastante bien.
fuente
¿Qué hay de este?
Hasta donde yo sé, GetRange () es lineal en términos de número de elementos tomados. Entonces esto debería funcionar bien.
fuente
Esta es una vieja pregunta, pero esto es con lo que terminé; enumera lo enumerable solo una vez, pero crea listas para cada una de las particiones. No sufre un comportamiento inesperado cuando
ToArray()
se llama como algunas de las implementaciones:fuente
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int chunkSize)
Encontramos que la solución de David B funcionó mejor. Pero lo adaptamos a una solución más general:
fuente
La siguiente solución es la más compacta que se me ocurre: O (n).
fuente
Código antiguo, pero esto es lo que he estado usando:
fuente
Si la lista es del tipo system.collections.generic, puede usar el método "CopyTo" disponible para copiar elementos de su matriz a otras sub-matrices. Usted especifica el elemento inicial y el número de elementos para copiar.
También puede hacer 3 clones de su lista original y usar el "RemoveRange" en cada lista para reducir el tamaño de la lista al tamaño que desee.
O simplemente cree un método auxiliar para hacerlo por usted.
fuente
Es una solución antigua pero tenía un enfoque diferente. Utilizo
Skip
para moverme al desplazamiento deseado yTake
extraer el número deseado de elementos:fuente
Para cualquier persona interesada en una solución empaquetada / mantenida, la biblioteca MoreLINQ proporciona el
Batch
método de extensión que coincide con el comportamiento solicitado:La
Batch
implementación es similar a la respuesta de Cameron MacFarland , con la adición de una sobrecarga para transformar el fragmento / lote antes de regresar, y funciona bastante bien.fuente
Usando particiones modulares:
fuente
Solo pongo mis dos centavos. Si desea "agrupar" la lista (visualizar de izquierda a derecha), puede hacer lo siguiente:
fuente
Otra forma es usar el operador Rx Buffer
fuente
fuente
Tomé la respuesta principal y lo convertí en un contenedor de COI para determinar dónde dividir. (¿ Para quién realmente busca dividirse solo en 3 elementos, al leer esta publicación mientras busca una respuesta? )
Este método permite dividir en cualquier tipo de elemento según sea necesario.
Entonces, para el OP, el código sería
fuente
Tan performatico como el enfoque de Sam Saffron .
}
fuente
Puede trabajar con generadores infinitos:
Código de demostración: https://ideone.com/GKmL7M
Pero en realidad preferiría escribir el método correspondiente sin linq.
fuente
¡Mira esto! Tengo una lista de elementos con un contador de secuencia y fecha. Cada vez que se reinicia la secuencia, quiero crear una nueva lista.
Ex. lista de mensajes.
Quiero dividir la lista en listas separadas a medida que se reinicia el contador. Aquí está el código:
fuente
Para insertar mis dos centavos ...
Al usar el tipo de lista para la fuente a fragmentar, encontré otra solución muy compacta:
fuente