381 votos

Lista dividida en sublistas con LINQ

Creo que este es otro fácil para usted LINQ maestros de allí. ¿Hay alguna manera de que se me separe de Lista en varias listas separadas de SomeObject, utilizando el elemento de índice como el delimitador de cada división?

Permítanme ejemplificar: Tengo un List<SomeObject> y necesito List<List<SomeObject>> o List<SomeObject>[], de modo que cada una de estas listas contienen un grupo de 3 elementos de la lista original (secuencialmente).

por 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 me gustaría necesidad de las listas resultantes de tamaño para ser un parámetro de esta función.

Es posible??

381voto

JaredPar Puntos 333733

Pruebe el siguiente código.

 public static List<List<object>> Split(List<object> 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 primer grupo los elementos de índices. Dividiendo por tres tiene el efecto de agrupar en grupos de 3 A continuación, convertir cada grupo a una lista y la lista de IEnumerable a una Lista de Lista de

324voto

CaseyB Puntos 1709

Esta pregunta es un poco viejo, pero me acaba 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);
    }
}
 

100voto

Sam Saffron Puntos 56236

En general, el enfoque sugerido por TickleMeElmo funciona bien, de hecho si estás de paso en un List<T> es difícil de fallo se, quizás yo la cambiaría 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;
   }
}

Que va a evitar la masiva convocatoria de las cadenas. Sin embargo, este enfoque tiene una falla general. Se materializa de dos enumeraciones por trozo, para poner de relieve el problema de intentar ejecutar:

foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
   Console.WriteLine(item);
}
// wait forever 

Para superar esto, podemos tratar de Cameron enfoque, que pasa la prueba anterior en el vuelo de colores, ya que sólo camina por la enumeración de una vez.

Problema es que tiene otro defecto, se materializa cada elemento en cada fragmento, el problema con este enfoque es que se puede ejecutar de alta en la 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

Por último, la aplicación debe ser capaz de manejar el orden de iteración de fragmentos, por ejemplo:

Enumerable.Range(1,3).Chunk(2).Reverse.ToArray()
// should return [3],[1,2]

Y muchos más óptimas soluciones como mi primera revisión de esta respuesta no existe. El mismo problema puede ser visto en casperOne optimizada de respuesta.

Para abordar todas estas cuestiones, puede utilizar los siguientes:

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 ronda de optimizaciones podría introducir por fuera de la orden de iteración de trozos, que está fuera del ámbito de este capítulo.

En cuanto a qué método elegir? Esto depende totalmente de el problema que están tratando de resolver. Si usted no está preocupado con el primer defecto de la respuesta simple es increíblemente atractivo.

Nota : como con la mayoría de los métodos, esto no es seguro para multi threading, cosas extrañas pasan, si usted desea hacerlo hilo seguro que sería necesario enmendar EnumeratorWrapper.

64voto

casperOne Puntos 49736

Usted podría usar un número de consultas que utilizan Take y Skip, pero que habría que añadir también el número de iteraciones en la lista original, creo.

Más bien, creo que se debería crear un iterador de su cuenta, 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;
   }
}

Puede llamar a esto y es LINQ habilitada para que pueda realizar otras operaciones sobre la resultante de las secuencias.


A la luz de la respuesta de Sam, sentí que había una manera más fácil de hacer esto sin:

  • Iterar a través de la lista otra vez (que yo no hice originalmente)
  • Materialización de los elementos en grupos antes de la liberación de la porción (para grandes fragmentos de artículos, no habría problemas de memoria)
  • Todo el código que Sam publicado

Dicho esto, he aquí otro paso, que he codificado en un método de extensión para IEnumerable<T> llamados 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);
}

Nada sorprendente, básica de comprobación de errores.

Pasar 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, se obtiene el IEnumerator<T> y manualmente se itera a través de cada elemento. Comprueba si hay elementos para ser enumerados. Después de cada fragmento se enumeran a través de, si no son los elementos de la izquierda, se rompe.

Una vez que se detecta que hay elementos en la secuencia, se delega la responsabilidad de la interna IEnumerable<T> ejecución 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());
}

Desde MoveNext fue llamado ya en el IEnumerator<T> pasado a ChunkSequence, se obtiene el elemento devuelto por Current y luego se incrementa el recuento, asegurándose de que nunca para devolver más de chunkSize elementos y pasar a la siguiente elemento de la secuencia después de cada iteración (pero corto circuito si el número de elementos que se produjo supera el fragmento el tamaño).

Si no hay ningún elemento a la izquierda, luego el InternalChunk método de hacer otro pase en el bucle externo, pero cuando MoveNext es llamada una segunda vez, todavía te devuelven false, como por la documentación (el énfasis es mío):

Si MoveNext pasa a la final de la colección, el enumerador es coloca después del último elemento de la colección y MoveNext devuelve false. Cuando el enumerador es en esta posición, posterior las llamadas a MoveNext también return false hasta que se Restablezca se llama.

En este punto, el bucle se romperá, y la secuencia de secuencias terminará.

Esta es una sencilla prueba:

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 usted no drenar toda la secuencia o descanso en cualquier punto en el padre de la secuencia. Esta es una advertencia importante, pero si el caso de uso, es que se consumen cada elemento de la secuencia de secuencias, entonces esto va a funcionar para usted.

Además, se va a hacer cosas extrañas, si juegas con el fin de, como Sam hizo en un momento dado.

7voto

David B Puntos 53123

Aquí está una rutina lista división 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;
}
 

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X