36

Linq Select 5 items each time based on our enumerator

Our List e.g "theList" has 100 items, Want to go through the list and Select 5 items in each iterations,

Sample Code, which we want to change this into our desired result :

        theList = dt.AsEnumerable()
            .Select(row => new CustItem
            {
                Name = row.Field<string>("tName"),
                Title = row.Field<string>("tTitle"),
            }).ToList();

We should Iterate it within a loop and process on the selected 5 items each time, or pass it to our other methods :

something like it :

for (int i=0; i < 20 ; i++)

I want to use "i" enumerator on the linq select statement and make a multiplicity to make the boundaries of our new resultset.

Kasrak
  • 1,509
  • 4
  • 24
  • 48
  • http://stackoverflow.com/questions/8083668/linq-performance-elementat-count-vs-foreach http://stackoverflow.com/questions/2342167/linq-to-entities-does-not-recognize-the-method-elementati these 2 links may help you... –  Jul 11 '12 at 07:24

5 Answers5

41
for (int i=0; i < 20 ; i++)
{
    var fiveitems = theList.Skip(i*5).Take(5);
}
Govind Malviya
  • 13,627
  • 17
  • 68
  • 94
40

It sounds like you want the Batch operator from MoreLINQ:

foreach (var batch in query.Batch(5))
{
    foreach (var item in batch)
    {
        ...
    } 
}
Edward
  • 3,292
  • 1
  • 27
  • 38
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Hi Jon, Seems to be interesting, but needed it to be ordered, Is it ? – Kasrak Jul 11 '12 at 07:16
  • 2
    Hi Jon, Can you please tell that is there any thing wrong with approach of Govind? – Asif Mushtaq Jul 11 '12 at 07:18
  • I'm interested to know any difference it has or the final comment by Jon, Although will have a deep look at the MoreLinq – Kasrak Jul 11 '12 at 07:21
  • 5
    @Asif: Well, that will iterate over the source multiple times, which may not be ideal. It's okay if this really is a `List`, but if it's something you *don't* want to put into memory (e.g. you're streaming lines from a text file) you don't want to start reading the file again for each batch. – Jon Skeet Jul 11 '12 at 07:24
  • @Sypress: Ordered in what way? The `Batch` operator will give you the batches in the same order as they occur in the original data - see the implementation for more details. – Jon Skeet Jul 11 '12 at 07:25
  • Want to know could I use "for" instead of "foreach" in the first line of the code you offered ? cause I need an "enumerator identifier" (something like the "i"), Maybe in addition I make it as a function which you give a number and it select based on the number. – Kasrak Jul 11 '12 at 07:30
  • 2
    You could use Jon's "SmartEnumerable" extension: http://msmvps.com/blogs/jon_skeet/archive/2007/07/27/smart-enumerations.aspx – Matthew Watson Jul 11 '12 at 07:49
  • @Jon Skeet your info is valuable, I looked at MoreLinq, couldn't find the fact that Does every "batch" got the index of the List we are iterating ? – Kasrak Jul 11 '12 at 09:12
  • 1
    @Sypress: No, there's no indicator of a "batch number" at the moment, although as Matthew says, you could use SmartEnumerable if you want. – Jon Skeet Jul 11 '12 at 09:26
34

You can also do this with pure linq by taking advantage of integer arithmetic and the GroupBy method:

int blockSize = 5;
var group = theList.Select((x, index) => new { x, index })
                   .GroupBy(x => x.index / blockSize, y => y.x);

foreach (var block in group)
{
    // "block" will be an instance of IEnumerable<T>
    ...
}

There are a number of advantages to this approach which aren't necessarily immediately apparent:

  • Execution is deferred, so it's efficient when you're working with conditional processing
  • You don't need to know the length of the collection, thus avoiding multiple enumeration while also being generally cleaner than other approaches
Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
Simon MᶜKenzie
  • 8,344
  • 13
  • 50
  • 77
  • Thanks it could help also, +1, could you tell also a bit on the x,y,blocksize and the index ? The index seems to be the starting point in the collection, what about the others ? also a problem here is that we need to know the index of the item within the Enumerable List which we selected, Here should be the "group". I haven't tried this way yet. – Kasrak Jul 12 '12 at 11:06
  • 2
    The overload of `Select` which I used takes a 2-parameter `Func`, where the parameters are the item (as with a normal `Select`) and its index. The code then projects that into a new datatype which contains the original item _with_ its index. The `GroupBy` is simple - since integer division always rounds down, `index/blocksize` will be the same for each item in a block (i.e. '0/5 == 1/5 == 2/5 == 3/5 == 4/5'). My last step, `y => y.x` just extracts the original item out again, but if you want to retain the index, don't include that final lambda. – Simon MᶜKenzie Jul 12 '12 at 12:14
  • The best answer I found! Thanks! – SedJ601 Nov 14 '20 at 18:16
4

Try this one:

 for (int i = 0; i < list.Count; i = i + 5) {
                var fiveitems = list.Skip(i).Take(5);
                foreach(var item in fiveitems)
                {

                }
            }
Nalan Madheswaran
  • 10,136
  • 1
  • 57
  • 42
3

The following static extension method would make it a straightforward process to break up a list into multiple lists of a specific batch size.

    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int noOfItemsPerBatch)
    {
        decimal iterations = items.Count() / noOfItemsPerBatch;

        var roundedIterations = (int)Math.Ceiling(iterations);

        var batches = new List<IEnumerable<T>>();

        for (int i = 0; i < roundedIterations; i++)
        {
            var newBatch = items.Skip(i * noOfItemsPerBatch).Take(noOfItemsPerBatch).ToArray();
            batches.Add(newBatch);
        }

        return batches;
    }

Example of use

var items = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var batchedItems = items.Batch(4);

Assert.AreEqual(batchedItems.Count() == 3);
Assert.AreEqual(batchedItems[0].Count() == 4);
Assert.AreEqual(batchedItems[1].Count() == 4);
Assert.AreEqual(batchedItems[2].Count() == 2);
Jamie Pearcey
  • 345
  • 1
  • 10