4

I would like to write a piece of for loop which would go through an existing list and take 20 items out of that list each time it iterates.

So something like this:

  • If filteredList list contains let's say 68 items...
  • First 3 loops would take 20 times each and then in last 4th iteration it would take the rest of the 8 items that are residing in the list...

I have written something like this:

var allResponses= new List<string>();
for (int i = 0; i < filteredList.Count(); i++)
{
    allResponses.Add(GetResponse(filteredList.Take(20).ToList()));
}

Where assuming filteredList is a list that contains 68 items. I figured that this is not a way to go because I don't want to loop to the collections size, but instead of 68 times, it should be 4 times and that I take 20 items out of the list each time... How could I do this?

kayess
  • 3,384
  • 9
  • 28
  • 45
User987
  • 3,663
  • 15
  • 54
  • 115

4 Answers4

6

You are pretty close - just add a call to Skip, and divide Count by 20 with rounding up:

var allResponses= new List<string>();
for (int i = 0; i < (filteredList.Count+19) / 20; i++) {
    allResponses.Add(GetResponse(filteredList.Skip(i*20).Take(20).ToList()));
}

The "add 19, divide by 20" trick provides an idiomatic way of taking the "ceiling" of integer division, instead of the "floor".

Edit: Even better (Thanks to Thomas Ayoub)

var allResponses= new List<string>();
for (int i = 0 ; i < filteredList.Count ; i = i + 20) {
    allResponses.Add(GetResponse(filteredList.Skip(i).Take(20).ToList()));
}
Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Why not use `(int i = 0; i < filteredList.Count; i = i + 20)` and `Skip(i)`? – Thomas Ayoub Apr 10 '17 at 13:01
  • @ThomasAyoub Thank you, that's indeed simpler and better. – Sergey Kalinichenko Apr 10 '17 at 13:07
  • @dasblinkenlight that will work even in the case of list having 68 items, where as the last batch would be 8 items ? P.S. I mean the 2nd case as well, from Thomas.. ? – User987 Apr 10 '17 at 13:09
  • 2
    @User987 you'll Skip 60 then take 20 which will end up to a take of 8, perfectly fine, dasblinkenlight: kudos to you – Thomas Ayoub Apr 10 '17 at 13:09
  • @User987 you can have a look at the [`Take(int)`](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,482) implementation – Thomas Ayoub Apr 10 '17 at 13:13
  • 2
    This will work fine as long as `filteredList` isn't a sequence that will be re-evaluated each time it's iterated (i.e. a List or Array would be fine). If the sequence has only linear access mechanics for the Skip() then of course this becomes an O(N^2) solution instead of an O(N) one. Just something to be aware of. – Matthew Watson Apr 10 '17 at 13:25
2

You simply have to calculate the number of pages:

const in PAGE_SIZE = 20;
int pages = filteredList.Count() / PAGE_SIZE
            + (filteredList.Count() % PAGE_SIZE > 0 ? 1 : 0)
            ;

The last part makes sure that with 21, there will be added 1 page above the previously calculated page size (since 21/20 = 1).

Or, when using MoreLINQ, you could simply use a Batch call.

Community
  • 1
  • 1
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
0

I suggest a simple loop with page adding on 0, 20, 40... iterations without Linq and complex modular operations:

int pageSize = 20;

List<String> page = null;

for (int i = 0; i < filteredList.Count; ++i) {
  // if page reach pageSize, add a new one 
  if (i % pageSize == 0) {
    page = new List<String>(pageSize);   

    allResponses.Add(page); 
  }

  page.Add(filteredList[i]); 
}
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
0

i needed to do same but i needed to skip 10 after each element so i wrote this simple code

List<Location2D> points = new List<Location2D>();
                points.ForEach(p =>
                {
                    points.AddRange(path.Skip((int)points.Count * 10).Take(1));
                });
                if (!points.Contains(path.LastOrDefault()))
                    points.Add(path.LastOrDefault());
Mr.PoP
  • 1
  • 2