Diviser la liste en sous-listes avec LINQ

est-ce que je peux séparer un List<SomeObject> en plusieurs listes distinctes de SomeObject , en utilisant l'index des articles comme délimiteur de chaque division?

laissez-moi illustrer:

j'ai un List<SomeObject> et j'ai besoin d'un List<List<SomeObject>> ou List<SomeObject>[] , de sorte que chacune de ces listes résultant contiendra un groupe de 3 éléments de la liste d'origine (séquentiellement).

par exemple.:

  • Liste Originale: [a, g, e, w, p, s, q, f, x, y, i, m, c]

  • listes résultantes: [a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

j'aurais aussi besoin que la taille des listes résultantes soit un paramètre de cette fonction.

331
demandé sur Draken 2009-01-07 05:43:22

26 réponses

essayez le code suivant.

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();
}

l'idée est de regrouper d'abord les éléments par index. En divisant par trois a pour effet de les regrouper en groupes de 3. Convertissez ensuite chaque groupe en une liste et le IEnumerable de List en un List de List s

325
répondu JaredPar 2016-01-14 21:34:12

cette question est un peu vieille, mais je viens de l'écrire, et je pense qu'elle est un peu plus élégante que les autres solutions proposées:

/// <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);
    }
}
286
répondu CaseyB 2011-06-15 18:40:09

en général l'approche suggérée par CaseyB fonctionne très bien, en fait si vous passez dans un List<T> il est difficile de le blâmer, peut-être que je le changerais en:

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;
   }
}

qui évitera les chaînes d'appel massives. Néanmoins, cette approche présente un défaut général. Il matérialise deux énumérations par morceau, pour mettre en évidence la question essayer d'exécuter:

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

Pour surmonter cela, nous pouvons essayer L'approche de Cameron, qui réussit le test ci-dessus avec brio puisqu'elle ne franchit le dénombrement qu'une seule fois.

le problème est qu'il a un défaut différent, il matérialise chaque élément dans chaque morceau, le problème avec cette approche est que vous courez haut sur la mémoire.

pour illustrer que d'essayer de courir:

foreach (var item in Enumerable.Range(1, int.MaxValue)
               .Select(x => x + new string('x', 100000))
               .Clump(10000).Skip(100).First())
{
   Console.Write('.');
}
// OutOfMemoryException

enfin, toute implémentation devrait être capable de gérer les itérations hors ordre de morceaux, par exemple:

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

beaucoup de solutions hautement optimales comme ma première révision de cette réponse a échoué là. La même question peut être vu dans casperOne optimisé réponse.

pour répondre à toutes ces questions, vous pouvez utiliser ce qui suit:

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();


        }

    }
}

il y a aussi une série d'optimisations que vous pourriez introduire pour des itérations hors-ordre de morceaux, qui est hors de portée ici.

Quelle méthode choisir? Il dépend entièrement du problème que vous essayez de résoudre. Si vous n'êtes pas concerné par le premier défaut, la réponse simple est incroyablement attrayant.

Note comme avec la plupart des méthodes, ce n'est pas sûr pour multi threading, les choses peuvent devenir bizarres si vous souhaitez le rendre thread sûr, vous auriez besoin de modifier EnumeratorWrapper .

91
répondu Sam Saffron 2018-03-02 15:22:08

Vous pourrait utiliser un certain nombre de requêtes qui utilisent des Take et Skip , mais ce serait ajouter un trop grand nombre d'itérations sur la liste d'origine, je crois.

plutôt, je pense que vous devriez créer un itérateur de votre propre, comme cela:

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;
   }
}

vous pouvez alors appeler cela et il est activé LINQ de sorte que vous pouvez effectuer d'autres opérations sur les séquences résultantes.


à la lumière de réponse de Sam , j'ai senti qu'il y avait une façon plus facile de faire ceci sans:

  • parcourant la liste à nouveau (ce qui je ne l'ai pas fait à l'origine)
  • matérialiser les articles en groupes avant de libérer le morceau (pour les gros morceaux d'articles, il y aurait des problèmes de mémoire)
  • tout le code que Sam a posté

qui dit, Voici un autre passage, que j'ai codifié dans une méthode d'extension à IEnumerable<T> appelé 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);
}

rien de surprenant là-haut, juste une simple vérification d'erreur.

passer à 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);
}

fondamentalement, il obtient le IEnumerator<T> et itère manuellement à travers chaque élément. Il vérifie pour voir si il y a des points à énumérer. Après chaque morceau est énuméré à travers, s'il n'y a pas d'articles restants, il éclate.

une fois qu'il détecte qu'il ya des éléments dans la séquence, il délègue la responsabilité de la mise en œuvre interne IEnumerable<T> à 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());
}

puisque MoveNext était déjà appelé le IEnumerator<T> passé à ChunkSequence , il donne l'article retourné par Current puis augmente le nombre, en s'assurant de ne jamais retourner plus de chunkSize articles et de passer à l'article suivant dans la séquence après chaque itération (mais court-circuité si le nombre d'articles fournis dépasse la taille du morceau).

S'il ne reste plus aucun article, alors la méthode InternalChunk fera un autre passage dans la boucle extérieure, mais quand MoveNext est appelé une deuxième fois, il retournera toujours false, comme par la documentation (l'emphase est mienne):

si MoveNext passe la fin de la collection, le recenseur est positionné après le dernier élément de la collection et MoveNext renvoie la valeur false. lorsque le recenseur est à cette position, les les appels vers MoveNext renvoient également false jusqu'à ce que Reset soit appelé.

à ce point, la boucle se brisera, et la séquence de séquences va se terminer.

C'est un test 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();
    }
}

sortie:

Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,

une note importante, ce sera ne fonctionne pas si vous ne vidangez pas la séquence entière de l'enfant ou de briser à tout moment dans la séquence parent. Il s'agit d'une mise en garde importante, mais si votre cas d'utilisation est que vous consommerez chaque élément de la séquence de séquences, alors cela fonctionnera pour vous.

de plus, il va faire des choses étranges si vous jouez avec l'ordre, tout comme Sam's fait à un point .

61
répondu casperOne 2017-05-23 10:31:37

Ok, voilà mon point de vue:

  • complètement paresseux: des travaux sur l'infini enumerables
  • aucun intermédiaire copier/mise en mémoire tampon
  • O(n) délai d'exécution
  • fonctionne aussi lorsque les séquences internes ne sont que partiellement consommées

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());
}

Exemple D'Usage

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}}

explications

le code fonctionne en imitant deux itérateurs basés sur yield .

l'itérateur externe doit tenir compte du nombre d'éléments effectivement consommés par l'itérateur interne (en morceaux). Ceci est fait en fermant sur remaining avec innerMoveNext() . Éléments non consommés d'une partie sont éliminés avant le prochain morceau est donné par l'extérieur itérateur. Cela est nécessaire parce que sinon vous obtenez des résultats incohérents, lorsque les dénombrables internes ne sont pas (complètement) consommés (par exemple c3.Count() retournerait 6).

Note: la réponse a été mise à jour pour corriger les lacunes signalées par @aolszowka.

40
répondu 3dGrabber 2017-09-20 11:06:18

complètement paresseux, pas de comptage ou de la copie:

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;
     }
  }
}
13
répondu user1583558 2015-02-23 19:57:46

je pense que la suggestion suivante serait la plus rapide. Je sacrifie le lazyness de la source énumérable pour la capacité d'utiliser le tableau.Copier et connaître à l'avance la longueur de chacune de mes sous-listes.

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;
    }
}
12
répondu Marc-André Bertrand 2014-09-19 21:03:29

nous pouvons améliorer la solution de @JaredPar pour faire une véritable évaluation paresseuse. Nous utilisons une méthode GroupAdjacentBy qui fournit des groupes d'éléments consécutifs avec la même clé:

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

parce que les groupes sont donnés un par un, Cette solution fonctionne efficacement avec des séquences longues ou infinies.

8
répondu Colonel Panic 2012-07-11 23:19:27

du Système.Le logiciel interactif fournit à cet effet le logiciel Buffer() . Certains tests rapides montrent que la performance est similaire à la solution de Sam.

8
répondu dahlbyk 2014-12-22 22:26:30

j'ai écrit une méthode D'extension Clump il y a plusieurs années. Fonctionne très bien, et est la mise en œuvre la plus rapide ici. : P

/// <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;
        }
    }
}
7
répondu Cameron MacFarland 2012-05-03 06:42:07

Voici une liste de routine que j'ai écrite il y a quelques mois:

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;
}
6
répondu Amy B 2009-01-07 14:41:54

C'est une vieille question, mais c'est ce que j'ai; il énumère les énumérable qu'une seule fois, mais permet de créer des listes de chacune des partitions. Il ne souffre pas d'un comportement inattendu lorsque ToArray() est appelé comme le font certaines implémentations:

    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;
            }
        }
    }
5
répondu aolszowka 2014-05-13 13:56:30

je trouve que ce petit détail fait très bien le travail.

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;
    }
}
5
répondu erlando 2015-09-03 16:29:03

nous avons trouvé que la solution de David B fonctionnait le mieux. Mais nous l'avons adapté à une solution plus générale:

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
4
répondu mwjackson 2010-04-09 10:28:17

Cette solution suivante est le plus compact que je pouvais venir avec que est 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;
    }
}
4
répondu Marc-André Bertrand 2014-12-12 21:21:31

ancien code, mais c'est ce que j'ai utilisé:

    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;
        }
    }
4
répondu Robert McKee 2015-02-25 09:31:10

si la liste est de type système.collection.generic vous pouvez utiliser la méthode "CopyTo" disponible pour copier des éléments de votre tableau vers d'autres sous-tableaux. Vous spécifiez l'élément de départ et le nombre d'éléments à copier.

vous pouvez également faire 3 clones de votre liste originale et utiliser le" RemoveRange " sur chaque liste pour rétrécir la liste à la taille que vous voulez.

Ou tout simplement créer une méthode d'aide pour le faire pour vous.

3
répondu Jobo 2009-01-07 03:16:08

et celui-ci?

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();

autant Que je sache, GetRange() est linéaire en termes de nombre d'articles. Donc, cela devrait bien se comporter.

3
répondu Roman Pekar 2013-08-16 13:59:38

C'est une vieille solution, mais j'ai eu une approche différente. J'utilise Skip pour passer à l'offset désiré et Take pour extraire le nombre désiré d'éléments:

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));
}
2
répondu Bertrand 2017-09-06 15:32:53

utilisant le partitionnement modulaire:

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));
}
1
répondu Janosz G. 2013-09-30 09:07:17

je mets juste mes deux cents. Si vous voulez "bucket" la liste( visualisez de gauche à droite), vous pouvez faire ce qui suit:

 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;
    }
1
répondu user1883961 2017-08-30 15:27:49

j'ai pris la réponse principale et en ai fait un conteneur IOC pour déterminer où fractionner. ( Pour qui cherche vraiment à seulement réparties sur 3 éléments, à la lecture de ce post, alors à la recherche d'une réponse? )

Cette méthode permet de diviser sur n'importe quel type d'élément si nécessaire.

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();
}

donc pour le PO le code serait

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 );
0
répondu ΩmegaMan 2017-09-06 16:25:20

aussi performatique que l'approche 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;
    }
}

}

0
répondu leandromoh 2018-03-03 05:01:27

peut fonctionner avec des générateurs infinis:

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)

le code de Démonstration: 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

mais en fait je préférerais écrire la méthode correspondante sans linq.

0
répondu Qwertiy 2018-06-05 15:03:35

pour toute personne intéressée par une solution emballée/maintenue, la bibliothèque MoreLINQ fournit la méthode d'extension Batch qui correspond à votre comportement demandé:

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);

la Batch implementation est similaire à réponse de Cameron MacFarland , avec l'ajout d'une surcharge pour transformer le morceau/lot avant le retour, et effectue tout à fait bien.

0
répondu Kevinoid 2018-08-09 21:58:37

pour insérer mes deux cents...

en utilisant le type de liste pour la source à découper, j'ai trouvé une autre solution très compacte:

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;
}
-1
répondu Patrick 2015-01-13 10:16:25