Estoy intentando dividir una lista en una serie de listas más pequeñas.
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 los 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 uso la función en una lista de tamaño 144, la salida es:
Índice: 4, Tamaño: 120
Índice: 3, Tamaño: 114
Índice: 2, Tamaño: 114
Índice: 1, Tamaño: 114
Índice: 0, Tamaño: 114
Respuestas:
Versión genérica:
fuente
GetRange(3, 3)
Sugeriría usar este método de extensión para fragmentar la lista de origen en las sublistas por tamaño de fragmento especificado:
Por ejemplo, si divide la lista de 18 elementos por 5 elementos por fragmento, le da la lista de 4 sublistas con los siguientes elementos dentro: 5-5-5-3.
fuente
ToList()
s, y dejar que la evaluación perezosa haga su magia.qué tal si:
fuente
ToList
pero no me molestaría en tratar de optimizarlo: es tan trivial y poco probable que sea un cuello de botella. La principal ganancia 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 compleja..Skip(n)
itera sobre losn
elementos cada vez que se llama, aunque esto puede estar bien, es importante tener en cuenta el código crítico para el rendimiento. stackoverflow.com/questions/20002975/….Skip()
en la base de código de mi empresa y, aunque pueden no ser "óptimos", funcionan bien. Cosas como las operaciones de DB tardan mucho más de todos modos. Pero creo que es importante tener en cuenta que.Skip()
"toca" cada elemento <n en su camino en lugar de saltar directamente al enésimo elemento (como es de esperar). Si su iterador tiene efectos secundarios al tocar un elemento,.Skip()
puede ser la causa de errores difíciles de encontrar.La solución Serj-Tm está bien, también esta es la versión genérica como método de extensión para listas (póngala en una clase estática):
fuente
Encuentro la respuesta aceptada (Serj-Tm) más robusta, pero me gustaría sugerir una versión genérica.
fuente
La biblioteca MoreLinq tiene un método llamado
Batch
El resultado es
ids
se dividen en 5 trozos con 2 elementos.fuente
Tengo un método genérico que tomaría cualquier tipo, incluido flotante, y ha sido probado por unidad, espero que ayude:
fuente
values.Count()
causará una enumeración completa y luegovalues.ToList()
otra. Es más seguro hacerlovalues = values.ToList()
, ya está materializado.Si bien muchas de las respuestas anteriores hacen el trabajo, todas fallan horriblemente en una secuencia interminable (o una secuencia realmente larga). La siguiente es una implementación completamente en línea que garantiza la mejor complejidad de tiempo y memoria posible. Solo iteramos la fuente enumerable exactamente una vez y utilizamos el rendimiento para la evaluación diferida. El consumidor podría tirar la lista en cada iteración haciendo que la huella de la memoria sea igual a la de la lista con el
batchSize
número de elementos.EDITAR: justo ahora, al darme cuenta de que el OP pregunta por dividir a un
List<T>
más pequeñoList<T>
, mis comentarios sobre infinitos enumerables no son aplicables al OP, pero pueden ayudar a otros que terminan aquí. Estos comentarios fueron en respuesta a otras soluciones publicadas que se utilizanIEnumerable<T>
como entrada para su función, pero enumeran la fuente enumerable varias veces.fuente
IEnumerable<IEnumerable<T>>
versión es mejor ya que no implica tantaList
construcción.IEnumerable<IEnumerable<T>>
es que es probable que la implementación dependa de que el consumidor enumere por completo cada enumerable interno producido. Estoy seguro de que se podría formular una solución para evitar ese problema, pero creo que el código resultante podría complicarse bastante rápido. Además, dado que es vago, solo estamos generando una sola lista a la vez y la asignación de memoria ocurre exactamente una vez por lista, ya que conocemos el tamaño por adelantado.Adición después del comentario muy útil de mhand al final
Respuesta original
Aunque la mayoría de las soluciones podrían funcionar, creo que no son muy eficientes. Supongamos que solo desea los primeros elementos de los primeros fragmentos. Entonces no querrás iterar sobre todos los elementos (miles de millones) en tu secuencia.
A continuación, se enumerarán dos veces como máximo: una para Take y otra para Skip. No enumerará más elementos de los que usará:
¿Cuántas veces enumerará esto la secuencia?
Supongamos que divide su fuente en trozos de
chunkSize
. Enumera solo los primeros N fragmentos. De cada fragmento enumerado solo enumerará los primeros elementos M.Any obtendrá el enumerador, realiza 1 MoveNext () y devuelve el valor devuelto después de desechar el enumerador. Esto se hará N veces
Según la fuente de referencia, esto hará algo como:
Esto no hace mucho hasta que comience a enumerar sobre el fragmento recuperado. Si obtiene varios Chunks, pero decide no enumerar el primer Chunk, el foreach no se ejecutará, como le mostrará su depurador.
Si decide tomar los primeros M elementos del primer fragmento, el rendimiento se ejecutará exactamente M veces. Esto significa:
Después de que se haya devuelto el primer fragmento, omitimos este primer fragmento:
Una vez más: echaremos un vistazo a la fuente de referencia para encontrar el
skipiterator
Como puede ver, las
SkipIterator
llamadasMoveNext()
una vez para cada elemento en el fragmento. No llamaCurrent
.Entonces, por fragmento, vemos que se hace lo siguiente:
Tomar():
Si se enumera el contenido: GetEnumerator (), un MoveNext y un Current por elemento enumerado, Dispose enumerator;
Saltar (): para cada fragmento que se enumera (NO el contenido del fragmento): GetEnumerator (), MoveNext () chunkSize veces, ¡no actual! Eliminar enumerador
Si observa lo que sucede con el enumerador, verá que hay muchas llamadas a MoveNext (), y solo llamadas a
Current
los elementos de TSource a los que realmente decide acceder.Si toma N trozos de tamaño chunkSize, llama a MoveNext ()
Si decide enumerar solo los primeros elementos M de cada fragmento recuperado, debe llamar a MoveNext M veces por fragmento enumerado.
El total
Entonces, si decides enumerar todos los elementos de todos los fragmentos:
Si MoveNext es mucho trabajo o no, depende del tipo de secuencia de origen. Para listas y matrices es un simple incremento de índice, con quizás una verificación fuera de rango.
Pero si su IEnumerable es el resultado de una consulta a la base de datos, asegúrese de que los datos realmente se materialicen en su computadora, de lo contrario, los datos se recuperarán varias veces. DbContext y Dapper transferirán correctamente los datos al proceso local antes de poder acceder a ellos. Si enumera la misma secuencia varias veces, no se obtiene varias veces. Dapper devuelve un objeto que es una Lista, DbContext recuerda que los datos ya están recuperados.
Depende de su repositorio si es aconsejable llamar a AsEnumerable () o ToLists () antes de comenzar a dividir los elementos en fragmentos
fuente
2*chunkSize
tiempos fuente ? Esto es mortal dependiendo de la fuente del enumerable (tal vez respaldado por DB u otra fuente no memorizada). Imagínese esto enumerable como entradaEnumerable.Range(0, 10000).Select(i => DateTime.UtcNow)
: obtendrá diferentes tiempos cada vez que enumere el enumerable, ya que no está memorizadoEnumerable.Range(0, 10).Select(i => DateTime.UtcNow)
. Al invocarAny
, volverá a calcular la hora actual cada vez. No es tan malo paraDateTime.UtcNow
, pero considere un enumerable respaldado por una conexión de base de datos / cursor sql o similar. He visto casos en los que se emitieron miles de llamadas de base de datos debido a que el desarrollador no comprendía las repercusiones potenciales de 'múltiples enumeraciones de un enumerable' - ReSharper proporciona una pista para esto tambiénfuente
fuente
¿Que tal este? La idea era usar solo un bucle. Y, quién sabe, tal vez solo esté usando implementaciones de IList a través de su código y no quiera enviarlo a la Lista.
fuente
Uno mas
fuente
fuente
fuente
Había encontrado esta misma necesidad, y usé una combinación de los métodos Skip () y Take () de Linq . Multiplico el número que tomo por el número de iteraciones hasta el momento, y eso me da el número de elementos para saltar, luego tomo el siguiente grupo.
fuente
Basado en Dimitry Pavlov, respondí que lo eliminaría
.ToList()
. Y también evita la clase anónima. En cambio, me gusta usar una estructura que no requiere una asignación de memoria de montón. (AValueTuple
también haría trabajo).Esto se puede usar como el siguiente, que solo itera sobre la colección una vez y tampoco asigna ninguna memoria significativa.
Si realmente se necesita una lista concreta, lo haría así:
fuente