Créer des lots dans linq

Quelqu'un peut-il suggérer un moyen de créer des lots d'une certaine taille à linq?

idéalement, je veux être capable d'effectuer des opérations en morceaux d'une certaine quantité configurable.

63
demandé sur BlakeH 2012-12-06 00:27:36

11 réponses

Vous n'avez pas besoin d'écrire de code. Utilisez la méthode par lots MoreLINQ , qui permet de grouper la séquence source en seaux de taille appropriée (MoreLINQ est disponible sous la forme D'un paquet NuGet que vous pouvez installer):

int size = 10;
var batches = sequence.Batch(size);

qui est mis en œuvre comme:

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
                  this IEnumerable<TSource> source, int size)
{
    TSource[] bucket = null;
    var count = 0;

    foreach (var item in source)
    {
        if (bucket == null)
            bucket = new TSource[size];

        bucket[count++] = item;
        if (count != size)
            continue;

        yield return bucket;

        bucket = null;
        count = 0;
    }

    if (bucket != null && count > 0)
        yield return bucket.Take(count);
}
70
répondu Sergey Berezovskiy 2016-08-25 19:50:49
public static class MyExtensions
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items,
                                                       int maxItems)
    {
        return items.Select((item, inx) => new { item, inx })
                    .GroupBy(x => x.inx / maxItems)
                    .Select(g => g.Select(x => x.item));
    }
}

et l'usage serait:

List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

foreach(var batch in list.Batch(3))
{
    Console.WriteLine(String.Join(",",batch));
}

sortie:

0,1,2
3,4,5
6,7,8
9
60
répondu L.B 2012-12-05 20:41:17

tout ce qui précède effectuer terriblement avec de grands lots ou faible espace de mémoire. Dû écrire mon propre qui pipeline (avis pas de point d'accumulation de n'importe où):

public static class BatchLinq {
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");

        using (IEnumerator<T> enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
                yield return TakeIEnumerator(enumerator, size);
    }

    private static IEnumerable<T> TakeIEnumerator<T>(IEnumerator<T> source, int size) {
        int i = 0;
        do
            yield return source.Current;
        while (++i < size && source.MoveNext());
    }
}

modifier: la question connue avec cette approche est que chaque lot doit être énuméré et énuméré complètement avant de passer au lot suivant. Par exemple, cela ne fonctionne pas:

//Select first item of every 100 items
Batch(list, 100).Select(b => b.First())
23
répondu Nick Whaley 2013-07-15 15:32:53

si vous commencez par sequence défini comme un IEnumerable<T> , et vous savez qu'il peut être énuméré en toute sécurité plusieurs fois (par exemple parce qu'il s'agit d'un tableau ou d'une liste), vous pouvez juste utiliser ce modèle simple pour traiter les éléments en lots:

while (sequence.Any())
{
    var batch = sequence.Take(10);
    sequence = sequence.Skip(10);

    // do whatever you need to do with each batch here
}
18
répondu Matthew Strawbridge 2017-01-06 22:23:54

il s'agit d'une mise en œuvre de lot entièrement paresseuse, à faible charge, à une seule fonction qui ne fait pas d'accumulation. Basé sur solution de Nick Whaley avec L'aide d'EricRoller.

itération vient directement de l'IEnumerable sous-jacent, de sorte que les éléments doivent être énumérés dans l'ordre strict, et accessibles pas plus d'une fois. Si certains éléments ne sont pas consommés dans une boucle interne, ils sont ignorés (et en essayant d'y accéder à nouveau par l'intermédiaire d'un itérateur enregistré lancera InvalidOperationException: Enumeration already finished. ).

vous pouvez tester un échantillon complet à . violon de réseau .

public static class BatchLinq
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");
        using (var enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
            {
                int i = 0;
                // Batch is a local function closing over `i` and `enumerator` that
                // executes the inner batch enumeration
                IEnumerable<T> Batch()
                {
                    do yield return enumerator.Current;
                    while (++i < size && enumerator.MoveNext());
                }

                yield return Batch();
                while (++i < size && enumerator.MoveNext()); // discard skipped items
            }
    }
}
3
répondu infogulch 2018-04-08 22:15:25

je me joins à vous très tard mais j'ai trouvé quelque chose de plus intéressant.

donc nous pouvons utiliser ici Skip et Take pour une meilleure performance.

public static class MyExtensions
    {
        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));
        }

        public static IEnumerable<T> Batch2<T>(this IEnumerable<T> items, int skip, int take)
        {
            return items.Skip(skip).Take(take);
        }

    }

J'ai vérifié avec 100000 dossiers. La boucle seulement prend plus de temps dans le cas de Batch

Code d'application de la console.

static void Main(string[] args)
{
    List<string> Ids = GetData("First");
    List<string> Ids2 = GetData("tsriF");

    Stopwatch FirstWatch = new Stopwatch();
    FirstWatch.Start();
    foreach (var batch in Ids2.Batch(5000))
    {
        // Console.WriteLine("Batch Ouput:= " + string.Join(",", batch));
    }
    FirstWatch.Stop();
    Console.WriteLine("Done Processing time taken:= "+ FirstWatch.Elapsed.ToString());


    Stopwatch Second = new Stopwatch();

    Second.Start();
    int Length = Ids2.Count;
    int StartIndex = 0;
    int BatchSize = 5000;
    while (Length > 0)
    {
        var SecBatch = Ids2.Batch2(StartIndex, BatchSize);
        // Console.WriteLine("Second Batch Ouput:= " + string.Join(",", SecBatch));
        Length = Length - BatchSize;
        StartIndex += BatchSize;
    }

    Second.Stop();
    Console.WriteLine("Done Processing time taken Second:= " + Second.Elapsed.ToString());
    Console.ReadKey();
}

static List<string> GetData(string name)
{
    List<string> Data = new List<string>();
    for (int i = 0; i < 100000; i++)
    {
        Data.Add(string.Format("{0} {1}", name, i.ToString()));
    }

    return Data;
}

c'est comme ça.

Première - 00:00:00.0708 , 00:00:00.0660

Second (prendre et sauter un) - 00:00:00.0008, 00:00:00.0008

2
répondu Unknown User 2016-04-12 06:57:20

même approche que MoreLINQ, mais en utilisant List au lieu de Array. Je n'ai pas fait de benchmarking, mais la lisibilité est plus importante pour certaines personnes:

    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        List<T> batch = new List<T>();

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

            if (batch.Count >= size)
            {
                yield return batch;
                batch.Clear();
            }
        }

        if (batch.Count > 0)
        {
            yield return batch;
        }
    }
2
répondu user4698855 2016-05-04 19:04:05

donc avec un chapeau fonctionnel dessus, cela semble trivial....mais en C#, il y a des inconvénients importants.

vous verriez probablement cela comme un déploiement de IEnumerable (google it et vous finirez probablement dans quelques docs Haskell, mais il peut y avoir quelques F# stuff utilisant unfold, si vous savez F#, louchez aux docs Haskell et cela aura du sens).

déplier est lié au pli ("agrégé") sauf qu'au lieu d'itérer par l'input IEnumerable, il itérates à travers les structures de données de sortie (c'est une relation similaire entre IEnumerable et IObservable, en fait je pense que IObservable ne mettre en œuvre un "unfold" appelé générer...)

en tout cas d'abord vous avez besoin d'un déplier méthode, je pense que cela fonctionne;

    static IEnumerable<T> Unfold<T, U>(Func<U, IEnumerable<Tuple<U, T>>> f, U seed)
    {
        var maybeNewSeedAndElement = f(seed);

        return maybeNewSeedAndElement.SelectMany(x => new[] { x.Item2 }.Concat(Unfold(f, x.Item1)));
    }

c'est un peu obtus car C# n'implémente pas certaines des choses que les langages fonctionnels tiennent pour acquises...mais il prend essentiellement une graine et puis génère un "peut-être" Réponse de la l'élément suivant dans L'IEnumerable et la graine suivante (peut-être n'existe pas en C#, donc nous avons utilisé IEnumerable pour le simuler), et concaténate le reste de la réponse(Je ne peux pas répondre du "O (n?)" la complexité de ce système).

une fois que vous avez fait cela alors;

    static IEnumerable<IEnumerable<T>> Batch<T>(IEnumerable<T> xs, int n)
    {
        return Unfold(ys =>
            {
                var head = ys.Take(n);
                var tail = ys.Skip(n);
                return head.Take(1).Select(_ => Tuple.Create(tail, head));
            },
            xs);
    }

ça a l'air assez propre...vous prenez les éléments "n" comme l'élément "suivant" dans L'IEnumerable, et la "queue" est le reste de la liste non traitée.

s'il y a rien dans la tête...vous êtes sur...vous ne retournez "Rien" (mais simulé comme un vide IEnumerable>)...sinon vous retourner l'élément de tête et la queue à traiter.

vous pouvez probablement le faire en utilisant IObservable, il y a probablement déjà une méthode de type "Batch", et vous pouvez probablement l'utiliser.

si le risque de débordement de la pile inquiète (il devrait probablement), alors vous devriez implémenter en F# (et il y a probablement une bibliothèque F# (FSharpX? déjà avec cette).

(j'ai seulement fait quelques tests rudimentaires de cela, il y a certainement quelques bugs).

1
répondu Mr D 2018-04-16 10:12:10

je sais que tout le monde a utilisé des systèmes complexes pour faire ce travail, et je ne comprends vraiment pas pourquoi. Take et skip permettront toutes ces opérations en utilisant la fonction de transformation select commune avec Func<TSource,Int32,TResult> . Comme:

public IEnumerable<IEnumerable<T>> Buffer<T>(IEnumerable<T> source, int size)=>
    source.Select((item, index) => source.Skip(size * index).Take(size)).TakeWhile(bucket => bucket.Any());
1
répondu Johni Michels 2018-10-03 04:03:48

j'ai écrit une implémentation sur mesure qui fonctionne sans linq et garantit une seule énumération sur les données. Il accomplit aussi tout cela sans exiger des listes de soutien ou des tableaux qui causent des explosions de mémoire au-dessus de grands ensembles de données.

voici quelques tests de base:

    [Fact]
    public void ShouldPartition()
    {
        var ints = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        var data = ints.PartitionByMaxGroupSize(3);
        data.Count().Should().Be(4);

        data.Skip(0).First().Count().Should().Be(3);
        data.Skip(0).First().ToList()[0].Should().Be(0);
        data.Skip(0).First().ToList()[1].Should().Be(1);
        data.Skip(0).First().ToList()[2].Should().Be(2);

        data.Skip(1).First().Count().Should().Be(3);
        data.Skip(1).First().ToList()[0].Should().Be(3);
        data.Skip(1).First().ToList()[1].Should().Be(4);
        data.Skip(1).First().ToList()[2].Should().Be(5);

        data.Skip(2).First().Count().Should().Be(3);
        data.Skip(2).First().ToList()[0].Should().Be(6);
        data.Skip(2).First().ToList()[1].Should().Be(7);
        data.Skip(2).First().ToList()[2].Should().Be(8);

        data.Skip(3).First().Count().Should().Be(1);
        data.Skip(3).First().ToList()[0].Should().Be(9);
    }

la méthode D'Extension pour la partition des données.

/// <summary>
/// A set of extension methods for <see cref="IEnumerable{T}"/>. 
/// </summary>
public static class EnumerableExtender
{
    /// <summary>
    /// Splits an enumerable into chucks, by a maximum group size.
    /// </summary>
    /// <param name="source">The source to split</param>
    /// <param name="maxSize">The maximum number of items per group.</param>
    /// <typeparam name="T">The type of item to split</typeparam>
    /// <returns>A list of lists of the original items.</returns>
    public static IEnumerable<IEnumerable<T>> PartitionByMaxGroupSize<T>(this IEnumerable<T> source, int maxSize)
    {
        return new SplittingEnumerable<T>(source, maxSize);
    }
}

c'est la classe d'exécution

    using System.Collections;
    using System.Collections.Generic;

    internal class SplittingEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        private readonly IEnumerable<T> backing;
        private readonly int maxSize;
        private bool hasCurrent;
        private T lastItem;

        public SplittingEnumerable(IEnumerable<T> backing, int maxSize)
        {
            this.backing = backing;
            this.maxSize = maxSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new Enumerator(this, this.backing.GetEnumerator());
        }

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

        private class Enumerator : IEnumerator<IEnumerable<T>>
        {
            private readonly SplittingEnumerable<T> parent;
            private readonly IEnumerator<T> backingEnumerator;
            private NextEnumerable current;

            public Enumerator(SplittingEnumerable<T> parent, IEnumerator<T> backingEnumerator)
            {
                this.parent = parent;
                this.backingEnumerator = backingEnumerator;
                this.parent.hasCurrent = this.backingEnumerator.MoveNext();
                if (this.parent.hasCurrent)
                {
                    this.parent.lastItem = this.backingEnumerator.Current;
                }
            }

            public bool MoveNext()
            {
                if (this.current == null)
                {
                    this.current = new NextEnumerable(this.parent, this.backingEnumerator);
                    return true;
                }
                else
                {
                    if (!this.current.IsComplete)
                    {
                        using (var enumerator = this.current.GetEnumerator())
                        {
                            while (enumerator.MoveNext())
                            {
                            }
                        }
                    }
                }

                if (!this.parent.hasCurrent)
                {
                    return false;
                }

                this.current = new NextEnumerable(this.parent, this.backingEnumerator);
                return true;
            }

            public void Reset()
            {
                throw new System.NotImplementedException();
            }

            public IEnumerable<T> Current
            {
                get { return this.current; }
            }

            object IEnumerator.Current
            {
                get { return this.Current; }
            }

            public void Dispose()
            {
            }
        }

        private class NextEnumerable : IEnumerable<T>
        {
            private readonly SplittingEnumerable<T> splitter;
            private readonly IEnumerator<T> backingEnumerator;
            private int currentSize;

            public NextEnumerable(SplittingEnumerable<T> splitter, IEnumerator<T> backingEnumerator)
            {
                this.splitter = splitter;
                this.backingEnumerator = backingEnumerator;
            }

            public bool IsComplete { get; private set; }

            public IEnumerator<T> GetEnumerator()
            {
                return new NextEnumerator(this.splitter, this, this.backingEnumerator);
            }

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

            private class NextEnumerator : IEnumerator<T>
            {
                private readonly SplittingEnumerable<T> splitter;
                private readonly NextEnumerable parent;
                private readonly IEnumerator<T> enumerator;
                private T currentItem;

                public NextEnumerator(SplittingEnumerable<T> splitter, NextEnumerable parent, IEnumerator<T> enumerator)
                {
                    this.splitter = splitter;
                    this.parent = parent;
                    this.enumerator = enumerator;
                }

                public bool MoveNext()
                {
                    this.parent.currentSize += 1;
                    this.currentItem = this.splitter.lastItem;
                    var hasCcurent = this.splitter.hasCurrent;

                    this.parent.IsComplete = this.parent.currentSize > this.splitter.maxSize;

                    if (this.parent.IsComplete)
                    {
                        return false;
                    }

                    if (hasCcurent)
                    {
                        var result = this.enumerator.MoveNext();

                        this.splitter.lastItem = this.enumerator.Current;
                        this.splitter.hasCurrent = result;
                    }

                    return hasCcurent;
                }

                public void Reset()
                {
                    throw new System.NotImplementedException();
                }

                public T Current
                {
                    get { return this.currentItem; }
                }

                object IEnumerator.Current
                {
                    get { return this.Current; }
                }

                public void Dispose()
                {
                }
            }
        }
    }
0
répondu leat 2017-12-02 23:10:34
    static IEnumerable<IEnumerable<T>> TakeBatch<T>(IEnumerable<T> ts,int batchSize)
    {
        return from @group in ts.Select((x, i) => new { x, i }).ToLookup(xi => xi.i / batchSize)
               select @group.Select(xi => xi.x);
    }
-2
répondu nichom 2015-07-02 16:33:10