Diviser une collection en «n» parties avec LINQ?

122

Existe-t-il un bon moyen de diviser une collection en plusieurs nparties avec LINQ? Pas nécessairement uniformément bien sûr.

Autrement dit, je veux diviser la collection en sous-collections, qui contiennent chacune un sous-ensemble d'éléments, où la dernière collection peut être déchiquetée.

Simon_Weaver
la source
1
Retagged: La question n'a rien à voir avec asp.net. Veuillez marquer vos questions de manière appropriée.
Comment voulez-vous qu'ils soient séparés, sinon même (en tenant compte de la fin, bien sûr)?
Marc Gravell
1
qui a lié à cette question? John était-ce toi? :-) tout à coup toutes ces réponses :-)
Simon_Weaver
Question associée Diviser la liste en sous-listes avec LINQ
Gennady Vanin Геннадий Ванин
@Simon_Weaver J'ai essayé de clarifier ce que vous demandez en fonction de la réponse acceptée. En fait, il existe de nombreuses façons de «diviser» une liste, notamment en décomposant chaque élément de la liste en ses éléments et en les plaçant dans des listes dites «parallèles».
jpaugh

Réponses:

127

Un linq pur et la solution la plus simple est comme indiqué ci-dessous.

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}
Muhammad Hasan Khan
la source
3
Vous pouvez faire: sélectionnez part.AsEnumerable () au lieu de select (IEnumerable <T>) part. C'est plus élégant.
tuinstoel le
2
Faire toutes ces opérations de module peut coûter un peu cher sur de longues listes.
Jonathan Allen
8
Il serait préférable d'utiliser la surcharge Select qui inclut l'index.
Marc Gravell
1
J'ai ajouté une réponse qui utilise la surcharge de sélection et la syntaxe de chaînage de méthode
reustmd
1
.AsEnumerable()n'est pas nécessaire, IGrouping <T> est déjà un IEnumerable <T>.
Alex
58

EDIT: D'accord, il semble que j'ai mal lu la question. Je l'ai lu comme "morceaux de longueur n" plutôt que "n morceaux". Doh! Envisager de supprimer la réponse ...

(Réponse originale)

Je ne pense pas qu'il existe une méthode intégrée de partitionnement, même si j'ai l'intention d'en écrire une dans mon ensemble d'ajouts à LINQ to Objects. Marc Gravell a une implémentation ici même si je la modifierais probablement pour renvoyer une vue en lecture seule:

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {             
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}
Jon Skeet
la source
Darn - beat me to it ;-p
Marc Gravell
3
Vous n'aimez vraiment pas ces "array [count ++]", hein ;-p
Marc Gravell
18
Merci de ne pas supprimer même si ce n'est pas une réponse pour l'OP, je voulais exactement la même chose - des morceaux de longueur n :).
Gishu
2
@Dejan: Non, ce n'est pas le cas. Notez l'utilisation de yield return. Il faut qu'un seul lot soit en mémoire à la fois, mais c'est tout.
Jon Skeet
1
@Dejan: D'accord - Je n'aimerais pas deviner comment il interagit avec le partitionnement Parallel LINQ, pour être honnête :)
Jon Skeet
39
static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}
reustmd
la source
28
J'ai une aversion irrationnelle pour Linq de style SQL, c'est donc ma réponse préférée.
piedar
1
@ manu08, j'ai essayé votre code, j'ai une liste var dept = {1,2,3,4,5}. Après avoir divisé le résultat, c'est comme dept1 = {1,3,5}et dept2 = { 2,4 }parts = 2. Mais le résultat dont j'ai besoin est dept1 = {1,2,3}etdept2 = {4,5}
Karthik Arthik
3
J'ai eu le même problème avec modulo, j'ai donc calculé la longueur de la colonne avec int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);puis fait la division avec .GroupBy(x => x.index / columnLength). Un inconvénient est que Count () énumère la liste.
goodeye le
24

Ok, je vais jeter mon chapeau dans le ring. Les avantages de mon algorithme:

  1. Aucun opérateur coûteux de multiplication, de division ou de module
  2. Toutes les opérations sont O (1) (voir note ci-dessous)
  3. Fonctionne pour la source IEnumerable <> (aucune propriété Count nécessaire)
  4. Facile

Le code:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

Comme indiqué dans les commentaires ci-dessous, cette approche ne répond pas réellement à la question initiale qui demandait un nombre fixe de sections de longueur approximativement égale. Cela dit, vous pouvez toujours utiliser mon approche pour résoudre la question d'origine en l'appelant de cette façon:

myEnum.Section(myEnum.Count() / number_of_sections + 1)

Lorsqu'elle est utilisée de cette manière, l'approche n'est plus O (1) car l'opération Count () est O (N).

Mike
la source
Brillant - la meilleure solution ici! Quelques optimisations: * Effacez la liste liée au lieu d'en créer une nouvelle pour chaque section. Une référence à la liste chaînée n'est jamais retournée à l'appelant, donc c'est totalement sûr. * Ne créez pas la liste chaînée avant d'avoir atteint le premier élément - de cette façon, il n'y a pas d'allocation si la source est vide
ShadowChaser
3
@ShadowChaser Selon MSDN, effacer la LinkedList est une complexité O (N) donc cela ruinerait mon objectif de O (1). Bien sûr, vous pouvez dire que le foreach est O (N) pour commencer ... :)
Mike
4
votre réponse est juste, mais la question est fausse. Votre réponse donne un nombre inconnu de morceaux avec une taille fixe pour chaque morceau. Mais OP veut une fonctionnalité Split où il donne un nombre fixe de morceaux avec n'importe quelle taille par morceau (espérons-le de tailles égales ou proches de l'égalité). Peut-être plus adapté ici stackoverflow.com/questions/3773403/…
nawfal
1
@Mike l'avez-vous comparé? J'espère que vous savez que O (1) ne signifie pas plus rapide, cela signifie seulement que le temps requis pour le partitionnement ne s'adapte pas. Je me demande simplement quelle est votre raison de vous en tenir aveuglément à O (1) alors qu'il peut être plus lent que les autres O (n) pour tous les scénarios de la vie réelle. Je l'ai même testé pour une liste folle de 10 ^ 8 et la mienne semblait encore plus rapide. J'espère que vous savez qu'il n'y a même pas de types de collection standard pouvant contenir 10 ^ 12 éléments ..
nawfal
1
@nawfal - Merci pour votre analyse détaillée, cela m'aide à rester sur mes gardes. Les listes liées en général sont connues pour leurs insertions de fin efficaces, c'est pourquoi je les ai sélectionnées ici. Cependant, je viens de le comparer et en effet la liste <> est beaucoup plus rapide. Je soupçonne qu'il s'agit d'une sorte de détail d'implémentation .NET, méritant peut-être une question StackOverflow distincte. J'ai modifié ma réponse pour utiliser List <> selon votre suggestion. La préallocation de la capacité de la liste garantit que l'insertion de fin est toujours O (1) et répond à mon objectif de conception d'origine. J'ai également basculé vers le .AsReadOnly () intégré dans .NET 4.5.
Mike
16

C'est la même chose que la réponse acceptée, mais une représentation beaucoup plus simple:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

La méthode ci-dessus divise un IEnumerable<T>nombre N de blocs de tailles égales ou proches de tailles égales.

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

La méthode ci-dessus divise un IEnumerable<T>en morceaux de taille fixe souhaitée, le nombre total de morceaux étant sans importance - ce qui n'est pas le sujet de la question.

Le problème avec la Splitméthode, en plus d'être plus lente, est qu'elle brouille la sortie dans le sens où le regroupement se fera sur la base du i'ième multiple de N pour chaque position, ou en d'autres termes, vous n'obtenez pas les morceaux dans l'ordre d'origine.

Presque chaque réponse ici ne préserve pas l'ordre, ou concerne le partitionnement et non le fractionnement, ou est tout simplement erronée. Essayez ceci qui est plus rapide, préserve l'ordre mais un peu plus verbeux:

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, 
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

La méthode équivalente pour une Partitionopération ici

nawfal
la source
6

J'utilise assez souvent la fonction Partition que j'ai postée plus tôt. Le seul inconvénient, c'est que ce n'est pas complètement en streaming. Ce n'est pas un problème si vous travaillez avec peu d'éléments dans votre séquence. J'avais besoin d'une nouvelle solution lorsque j'ai commencé à travailler avec plus de 100 000 éléments dans ma séquence.

La solution suivante est beaucoup plus complexe (et plus de code!), Mais elle est très efficace.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

Prendre plaisir!

Elmer
la source
Cette version rompt le contrat de IEnumerator. Il n'est pas valide de lancer InvalidOperationException lorsque Reset est appelé - je crois que de nombreuses méthodes d'extension LINQ reposent sur ce comportement.
ShadowChaser
1
@ShadowChaser Je pense que Reset () devrait lancer une NotSupportedException et tout irait bien. À partir de la documentation MSDN: «La méthode Reset est fournie pour l'interopérabilité COM. Elle n'a pas nécessairement besoin d'être implémentée; à la place, l'implémenteur peut simplement lever une NotSupportedException.»
jusqu'au
@toong Wow, vous avez raison. Je ne sais pas comment j'ai raté ça après tout ce temps.
ShadowChaser
C'est buggy! Je ne me souviens pas exactement, mais (pour autant que je me souvienne), il effectue une étape indésirable et peut entraîner des effets secondaires horribles (avec datareader par exemple). La meilleure solution est ici (Jeppe Stig Nielsen): stackoverflow.com/questions/13709626/…
SalientBrain
4

Fil intéressant. Pour obtenir une version en continu de Split / Partition, on peut utiliser des énumérateurs et produire des séquences de l'énumérateur en utilisant des méthodes d'extension. La conversion du code impératif en code fonctionnel à l'aide de yield est en effet une technique très puissante.

D'abord une extension d'énumérateur qui transforme un nombre d'éléments en une séquence paresseuse:

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

Et puis une extension énumérable qui partitionne une séquence:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

Le résultat final est une implémentation très efficace, en continu et paresseuse qui repose sur un code très simple.

Prendre plaisir!

Martin Fredriksson
la source
J'ai initialement programmé la même chose, mais le modèle se brise lorsque Reset est appelé sur l'une des instances imbriquées IEnumerable <T>.
ShadowChaser
1
Cela fonctionne-t-il toujours si vous énumérez uniquement la partition et non l'énumérable interne? puisque l'énumérateur interne est différé, aucun code pour l'interne (prise du courant) ne s'exécutera jusqu'à ce qu'il soit énuméré, donc movenext () ne sera appelé que par la fonction de partition externe, non? Si mes hypothèses sont vraies, cela peut potentiellement produire n partitions avec n éléments dans l'énumérable d'origine et les énumérables internes donneront des résultats inattendus
Brad
@Brad il "échouera" comme vous vous y attendez, similaire à certains des problèmes de ce thread stackoverflow.com/questions/419019/... (en particulier stackoverflow.com/a/20953521/1037948 )
drzaus
4

J'utilise ceci:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
    return instance
        .Select((value, index) => new { Index = index, Value = value })
        .GroupBy(i => i.Index / partitionSize)
        .Select(i => i.Select(i2 => i2.Value));
}
Elmer
la source
Veuillez expliquer pourquoi. J'utilise cette fonction sans aucun problème!
Elmer
relisez la question et voyez si vous obtenez n parties de longueur (presque) égales avec votre fonction
Muhammad Hasan Khan
@Elmer votre réponse est juste, mais la question est fausse. Votre réponse donne un nombre inconnu de morceaux avec une taille fixe pour chaque morceau (exactement comme Partition, le nom que vous lui avez donné). Mais OP veut une fonctionnalité Split où il donne un nombre fixe de morceaux avec n'importe quelle taille par morceau (espérons-le de tailles égales ou proches de l'égalité). Peut-être plus adapté ici stackoverflow.com/questions/3773403/…
nawfal
Je pense que vous pouvez simplement changer i.Index / partitionSize en i.Index% partitionSize et obtenir le résultat demandé. Je préfère également cela à la réponse acceptée car elle est plus compacte et lisible.
Jake Drew
2

Ceci est efficace en mémoire et reporte l'exécution autant que possible (par lot) et fonctionne en temps linéaire O (n)

    public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
    {
        List<T> batch = new List<T>(batchSize);
        foreach (var item in items)
        {
            batch.Add(item);

            if (batch.Count >= batchSize)
            {
                yield return batch;
                batch = new List<T>();
            }
        }

        if (batch.Count != 0)
        {
            //can't be batch size or would've yielded above
            batch.TrimExcess();
            yield return batch;
        }
    }
Brad
la source
2

Il y a beaucoup de bonnes réponses à cette question (et à ses cousins). J'en avais besoin moi-même et j'avais créé une solution conçue pour être efficace et tolérante aux erreurs dans un scénario où la collection source peut être traitée comme une liste. Il n'utilise aucune itération différée et peut donc ne pas convenir aux collections de taille inconnue susceptibles d'appliquer une pression mémoire.

static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
    IList<T[]> result = null;
    if (source != null && batchsize > 0)
    {
        var list = source as List<T> ?? source.ToList();
        if (list.Count > 0)
        {
            result = new List<T[]>();
            for (var index = 0; index < list.Count; index += batchsize)
            {
                var rangesize = Math.Min(batchsize, list.Count - index);
                result.Add(list.GetRange(index, rangesize).ToArray());
            }
        }
    }
    return result ?? Enumerable.Empty<T[]>().ToList();
}

static public void TestGetChunks()
{
    var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
    foreach (var chunk in ids.GetChunks(20))
    {
        Console.WriteLine("[{0}]", String.Join(",", chunk));
    }
}

J'ai vu quelques réponses à travers cette famille de questions qui utilisent GetRange et Math.Min. Mais je pense que dans l'ensemble, c'est une solution plus complète en termes de vérification des erreurs et d'efficacité.

rhaben
la source
1
   protected List<List<int>> MySplit(int MaxNumber, int Divider)
        {
            List<List<int>> lst = new List<List<int>>();
            int ListCount = 0;
            int d = MaxNumber / Divider;
            lst.Add(new List<int>());
            for (int i = 1; i <= MaxNumber; i++)
            {
                lst[ListCount].Add(i);
                if (i != 0 && i % d == 0)
                {
                    ListCount++;
                    d += MaxNumber / Divider;
                    lst.Add(new List<int>());
                }
            }
            return lst;
        }
Amit Sengar
la source
1

Great Answers, pour mon scénario, j'ai testé la réponse acceptée, et il semble qu'elle ne garde pas l'ordre. il y a aussi une excellente réponse de Nawfal qui maintient l'ordre. Mais dans mon scénario, je voulais diviser le reste de manière normalisée, toutes les réponses que j'ai vues se répartissent le reste ou au début ou à la fin.

Ma réponse prend également le reste se répandant de manière plus normalisée.

 static class Program
{          
    static void Main(string[] args)
    {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k.ToString());
        }
        var result = splitListIntoSmallerLists(input, 15);            
        int i = 0;
        foreach(var resul in result){
            Console.WriteLine("------Segment:" + i.ToString() + "--------");
            foreach(var res in resul){
                Console.WriteLine(res);
            }
            i++;
        }
        Console.ReadLine();
    }

    private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
    {
        if (i_numberOfSmallerLists <= 0)
            throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");

        int normalizedSpreadRemainderCounter = 0;
        int normalizedSpreadNumber = 0;
        //e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2          
        int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;                        
        int remainder = i_bigList.Count % i_numberOfSmallerLists;
        int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
        //In case remainder > 0 we want to spread the remainder equally between the others         
        if (remainder > 0)
        {
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);    
            }
            else
            {
                normalizedSpreadNumber = 1;
            }   
        }
        List<List<T>> retVal = new List<List<T>>(outputSize);
        int inputIndex = 0;            
        for (int i = 0; i < outputSize; ++i)
        {
            retVal.Add(new List<T>());
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
                inputIndex += minimumNumberOfPartsInEachSmallerList;
            }
            //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
            if (remainder > 0)
            {
                if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
                {
                    retVal[i].Add(i_bigList[inputIndex]);
                    remainder--;
                    inputIndex++;
                    normalizedSpreadRemainderCounter=0;
                }
                else
                {
                    normalizedSpreadRemainderCounter++;
                }
            }
        }
        return retVal;
    }      

}
Robocide
la source
0

Si l'ordre dans ces pièces n'est pas très important, vous pouvez essayer ceci:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;

var result =
   array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);

// or
var result2 =
   from i in array.Select((value, index) => new { Value = value, Index = index })
   group i.Value by i.Index % n into g
   select g;

Cependant, ils ne peuvent pas être convertis en IEnumerable <IEnumerable <int>> pour une raison quelconque ...

okutane
la source
Ça peut être fait. Au lieu de la diffusion directe, rendez simplement la fonction générique et appelez-la pour votre tableau int
nawfal
0

Ceci est mon code, joli et court.

 <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
     Dim result As New List(Of List(Of T))
     For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
         result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
     Next
     Return result
 End Function
Jonathan Allen
la source
0

C'est ma façon de lister les éléments et de casser ligne par colonne

  int repat_count=4;

  arrItems.ForEach((x, i) => {
    if (i % repat_count == 0) 
        row = tbo.NewElement(el_tr, cls_min_height);
    var td = row.NewElement(el_td);
    td.innerHTML = x.Name;
  });
IlPADlI
la source
0

Je cherchais un split comme celui avec une chaîne, donc toute la liste est divisée selon une règle, pas seulement la première partie, c'est ma solution

List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
     sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
    splitted.Add(sequence.Take(splitIndex).ToList() );
    sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}
Adel
la source
La prochaine fois, essayez: var nrs = Enumerable.Range (1,2000) .ToList ();
MBoros
0

Voici un petit ajustement pour le nombre d'éléments au lieu du nombre de pièces:

public static class MiscExctensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
    {
        return (
            list
            .Select((o, n) => new { o, n })
            .GroupBy(g => (int)(g.n / nbItems))
            .Select(g => g.Select(x => x.o))
        );
    }
}
JB
la source
-1
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };

int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;

var seqItems = from aItem in items
               group aItem by 
                            (itemIndex++ < nextGroup) 
                            ? 
                            nextGroup / groupSize
                            :
                            (nextGroup += groupSize) / groupSize
                            into itemGroup
               select itemGroup.AsEnumerable();

la source
-1

Je viens de tomber sur ce fil, et la plupart des solutions ici impliquent d'ajouter des éléments aux collections, matérialisant efficacement chaque page avant de la renvoyer. C'est mauvais pour deux raisons: premièrement, si vos pages sont volumineuses, il y a une surcharge de mémoire pour remplir la page, deuxièmement, il y a des itérateurs qui invalident les enregistrements précédents lorsque vous passez au suivant (par exemple si vous enveloppez un DataReader dans une méthode d'énumération) .

Cette solution utilise deux méthodes d'énumérateur imbriquées pour éviter d'avoir à mettre en cache des éléments dans des collections temporaires. Étant donné que les itérateurs externes et internes parcourent le même énumérable, ils partagent nécessairement le même énumérateur, il est donc important de ne pas avancer vers l'extérieur tant que vous n'avez pas terminé le traitement de la page actuelle. Cela dit, si vous décidez de ne pas parcourir complètement la page actuelle, lorsque vous passez à la page suivante, cette solution effectuera automatiquement une itération vers la limite de la page.

using System.Collections.Generic;

public static class EnumerableExtensions
{
    /// <summary>
    /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
    /// </summary>
    /// <typeparam name="T">The element type</typeparam>
    /// <param name="enumerable">The source enumerable</param>
    /// <param name="pageSize">The number of elements to return in each page</param>
    /// <returns></returns>
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
    {
        var enumerator = enumerable.GetEnumerator();

        while (enumerator.MoveNext())
        {
            var indexWithinPage = new IntByRef { Value = 0 };

            yield return SubPartition(enumerator, pageSize, indexWithinPage);

            // Continue iterating through any remaining items in the page, to align with the start of the next page
            for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
            }
        }
    }

    private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
    {
        for (; index.Value < pageSize; index.Value++)
        {
            yield return enumerator.Current;

            if (!enumerator.MoveNext())
            {
                yield break;
            }
        }
    }

    private class IntByRef
    {
        public int Value { get; set; }
    }
}
Jon G
la source
Cela ne fonctionne pas du tout! Le meilleur possible est ici stackoverflow.com/questions/13709626/ ...! Voir les commentaires.
SalientBrain