Diviser une liste en listes plus petites de taille N

J'essaie de diviser une liste en une série de listes plus petites.

Mon problème: Ma fonction pour diviser des listes ne les divise pas en listes de la bonne taille. Il devrait les diviser en listes de taille 30 mais à la place il les divise en listes de taille 114?

Comment faire en sorte que ma fonction divise une liste en X nombre de listes de taille 30 ou moins ?

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 j'utilise la fonction sur une liste de taille 144, alors la sortie est:

Index: 4, Taille: 120
Indice: 3, Taille: 114
Index: 2, Taille: 114
Index: 1, Taille: 114
Index: 0, Taille: 114

119
demandé sur fubo 2012-07-13 07:27:09

11 réponses

public static List<List<float[]>> splitList(List<float[]> locations, int nSize=30)  
{        
    var list = new List<List<float[]>>(); 

    for (int i=0; i < locations.Count; i+= nSize) 
    { 
        list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
    } 

    return list; 
} 

Version Générique:

public static IEnumerable<List<T>> splitList<T>(List<T> locations, int nSize=30)  
{        
    for (int i=0; i < locations.Count; i+= nSize) 
    { 
        yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); 
    }  
} 
146
répondu Serj-Tm 2017-03-03 17:49:22

Je suggère d'utiliser cette méthode d'extension pour découper la liste source dans les sous-Listes par taille de morceau spécifiée:

/// <summary>
/// Helper methods for the lists.
/// </summary>
public static class ListExtensions
{
    public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize) 
    {
        return source
            .Select((x, i) => new { Index = i, Value = x })
            .GroupBy(x => x.Index / chunkSize)
            .Select(x => x.Select(v => v.Value).ToList())
            .ToList();
    }
}

Par exemple, si vous supprimez la liste de 18 éléments par 5 éléments par bloc, elle vous donne la liste de 4 sous-listes avec les éléments suivants à l'intérieur: 5-5-5-3.

279
répondu Dmitry Pavlov 2017-11-21 13:18:09

Que diriez-vous de:

while(locations.Any())
{    
    list.Add(locations.Take(nSize).ToList());
    locations= locations.Skip(nSize).ToList();
}
28
répondu Rafal 2017-08-10 16:13:04

La solution Serj-Tm est bien, c'est aussi la version générique comme méthode d'extension pour les listes (mettez-la dans une classe statique):

public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30)
{
    List<List<T>> list = new List<List<T>>();
    for (int i = 0; i < items.Count; i += sliceSize)
        list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
    return list;
} 
10
répondu equintas 2016-01-11 18:30:03

Je trouve la réponse acceptée (Serj-Tm) la plus robuste, mais je voudrais suggérer une version générique.

   public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30)
    {
        var list = new List<List<T>>();

        for (int i = 0; i < locations.Count; i += nSize)
        {
            list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
        }

        return list;
    }
6
répondu Linas 2016-05-08 10:23:33

J'ai une méthode générique qui prendrait n'importe quel type include float, et elle a été testée à l'unité, j'espère que ça aide:

    /// <summary>
    /// Breaks the list into groups with each group containing no more than the specified group size
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="values">The values.</param>
    /// <param name="groupSize">Size of the group.</param>
    /// <returns></returns>
    public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null)
    {
        List<List<T>> result = new List<List<T>>();
        // Quick and special scenario
        if (values.Count() <= groupSize)
        {
            result.Add(values.ToList());
        }
        else
        {
            List<T> valueList = values.ToList();
            int startIndex = 0;
            int count = valueList.Count;
            int elementCount = 0;

            while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount)))
            {
                elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize;
                result.Add(valueList.GetRange(startIndex, elementCount));
                startIndex += elementCount;
            }
        }


        return result;
    }
5
répondu Tianzhen Lin 2012-07-13 03:39:48

Ajout après très utile commentaire de mhand à la fin

Réponse originale

Bien que la plupart des solutions puissent fonctionner, je pense qu'elles ne sont pas très efficaces. Supposons que vous ne voulez que les premiers éléments des premiers morceaux. Ensuite, vous ne voudriez pas itérer sur tous les éléments (zillion) de votre séquence.

Ce qui suit sera au maximum énumérer deux fois: une fois pour la prise et une fois pour le saut. Il n'énumérera pas plus d'éléments que vous ne le ferez utilisation:

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
    (this IEnumerable<TSource> source, int chunkSize)
{
    while (source.Any())                     // while there are elements left
    {   // still something to chunk:
        yield return source.Take(chunkSize); // return a chunk of chunkSize
        source = source.Skip(chunkSize);     // skip the returned chunk
    }
}

Combien de fois cela énumérera-t-il la séquence?

Supposons que vous divisez votre source en morceaux de chunkSize. Vous n'énumérez que les N premiers morceaux. De chaque morceau énuméré, vous n'énumérerez que les premiers M éléments.

While(source.Any())
{
     ...
}

L'Any obtiendra L'énumérateur, fera 1 MoveNext () et retournera la valeur retournée après avoir éliminé l'énumérateur. Cela sera fait N fois

yield return source.Take(chunkSize);

Selon la source de référence cela fera quelque chose comme:

public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
    return TakeIterator<TSource>(source, count);
}

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    foreach (TSource element in source)
    {
        yield return element;
        if (--count == 0) break;
    }
}

Cela ne fait pas beaucoup jusqu'à ce que vous commenciez à énumérer sur le morceau récupéré. Si vous récupérez plusieurs morceaux, mais décidez de ne pas énumérer sur le premier morceau, le foreach n'est pas exécuté, comme votre débogueur vous le montrera.

Si vous décidez de prendre les m premiers éléments du premier morceau, le rendement du rendement est exécuté exactement M fois. Cela signifie:

  • récupère l'énumérateur
  • appelez MoveNext () et M fois en cours.
  • éliminez le énumérateur

Après que le premier morceau a été retourné, nous sautons ce premier morceau:

source = source.Skip(chunkSize);

Encore une Fois: nous allons jeter un oeil à source de référence pour trouver l' skipiterator

static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
    using (IEnumerator<TSource> e = source.GetEnumerator()) 
    {
        while (count > 0 && e.MoveNext()) count--;
        if (count <= 0) 
        {
            while (e.MoveNext()) yield return e.Current;
        }
    }
}

Comme vous le voyez, le SkipIterator appelle MoveNext() Une fois pour chaque élément du bloc. Il n'appelle pas Current.

Donc, par morceau, nous voyons que ce qui suit est fait:

  • Tout(): GetEnumerator; 1 MoveNext(); Disposer Agent Recenseur;
  • Prendre():

    • rien si le contenu du morceau n'est pas énuméré.
    • Si le contenu est énuméré: GetEnumerator (), un MoveNext et un Current par élément énuméré, Dispose enumerator;

    • Skip (): pour chaque morceau énuméré (pas le contenu du morceau): GetEnumerator (), MoveNext () chunksize fois, pas de courant! Éliminer l'énumérateur

Si vous regardez ce qui se passe avec l'énumérateur, vous verrez qu'il il y a beaucoup d'appels à MoveNext(), et seulement des appels à Current pour les éléments TSource auxquels vous décidez réellement d'accéder.

Si vous prenez N morceaux de taille chunkSize, alors appelle MoveNext ()

  • N fois pour tout ()
  • pas encore de temps pour prendre, tant que vous n'énumérez pas les morceaux
  • N fois chunkSize pour Skip ()

Si vous décidez d'énumérer uniquement les m premiers éléments de chaque morceau récupéré, vous devez appeler MoveNext M fois par morceau énuméré.

Le total

MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)

Donc, si vous décidez d'énumérer tous les éléments de tous les morceaux:

MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once

Que MoveNext soit beaucoup de travail ou non, dépend du type de séquence source. Pour les listes et les tableaux, il s'agit d'un simple incrément d'index, avec peut-être une vérification hors plage.

, Mais si votre IEnumerable est le résultat d'une requête de base de données, assurez-vous que les données sont matérialisées sur votre ordinateur, sinon les données seront récupérées à plusieurs reprises. DbContext et Dapper transféreront correctement les données au processus local avant qu'il ne soit accessible. Si vous énumérez la même séquence plusieurs fois, elle n'est pas récupérée plusieurs fois. Dapper renvoie un objet qui est une liste, DbContext se souvient que les données sont déjà récupérées.

Cela dépend de votre référentiel s'il est sage d'appeler AsEnumerable() ou ToLists () avant de commencer à diviser les éléments en Morceaux

3
répondu Harald Coppoolse 2018-03-21 09:22:00

La bibliothèque MoreLinq a une méthode appelée Batch

List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements
int counter = 1;
foreach(var batch in ids.Batch(2))
{
    foreach(var eachId in batch)
    {
        Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId);
    }
    counter++;
}

Le résultat est

Batch: 1, Id: 1
Batch: 1, Id: 2
Batch: 2, Id: 3
Batch: 2, Id: 4
Batch: 3, Id: 5
Batch: 3, Id: 6
Batch: 4, Id: 7
Batch: 4, Id: 8
Batch: 5, Id: 9
Batch: 5, Id: 0

ids sont divisés en 5 morceaux avec 2 éléments.

2
répondu Sidron 2017-09-25 13:04:26
public static IEnumerable<IEnumerable<T>> SplitIntoSets<T>
    (this IEnumerable<T> source, int itemsPerSet) 
{
    var sourceList = source as List<T> ?? source.ToList();
    for (var index = 0; index < sourceList.Count; index += itemsPerSet)
    {
        yield return sourceList.Skip(index).Take(itemsPerSet);
    }
}
2
répondu Scott Hannen 2018-03-21 15:26:38

Alors que beaucoup de réponses ci-dessus font le travail, elles échouent toutes horriblement sur une séquence sans fin (ou une séquence très longue). Ce qui suit est une implémentation entièrement en ligne qui garantit le meilleur temps et la complexité de la mémoire possible. Nous n'itérons la source énumérable qu'une seule fois et utilisons yield return pour une évaluation paresseuse. Le consommateur pourrait jeter la liste à chaque itération rendant l'empreinte mémoire égale à celle de la liste w / batchSize Nombre de élément.

public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, int batchSize)
{
    using (var enumerator = enumerable.GetEnumerator())
    {
        List<T> list = null;
        while (enumerator.MoveNext())
        {
            if (list == null)
            {
                list = new List<T> {enumerator.Current};
            }
            else if (list.Count < batchSize)
            {
                list.Add(enumerator.Current);
            }
            else
            {
                yield return list;
                list = new List<T> {enumerator.Current};
            }
        }

        if (list?.Count > 0)
        {
            yield return list;
        }
    }
}

EDIT: je viens de réaliser que L'OP demande de casser un List<T> en plus petit List<T>, donc mes commentaires concernant les énumérables infinis ne sont pas applicables à L'OP, mais peuvent aider les autres qui finissent ici. Ces commentaires étaient en réponse à d'autres solutions publiées qui utilisent IEnumerable<T> comme entrée à leur fonction, mais énumèrent la source énumérable plusieurs fois.

1
répondu mhand 2018-03-20 22:15:05
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
{
    return items.Select((item, index) => new { item, index })
                .GroupBy(x => x.index / maxItems)
                .Select(g => g.Select(x => x.item));
}
-1
répondu Codester 2018-09-17 20:41:21