Search code examples
c#paginationienumerable

Implementing skip/take pagination via pageIndex/pageSize pagination when skip is not divisible by take


Suppose that I have some magically paginated black box class that retrieves data by using pageIndex and pageSize as follows:

public class PaginatedList<T>
{
    // ...

    // Chops up the internally stored list into pages of size "pageSize",
    // then returns the page stored at index "pageIndex".
    public IEnumerable<T> Get(int pageIndex, int pageSize)
    {
        // Magic black box code goes here.
    }

    // ...
}

Now suppose that I have a driver class that wants to use this PaginatedList class, but wants to achieve pagination by using skip and take parameters. Certainly, if the offset that I want to skip by happens to be divisible by the amount that I want to take, then I can achieve this by doing something like the following:

public class MyDriver
{
    // Bypass the first "skip" elements and return the next "take" elements.
    static IEnumerable<T> OffsetGet(PaginatedList<T> myList, int skip, int take)
    {
        // ASSERT: skip % take == 0 is true.
        return myList.Get(skip/take, take);
    }

    static void Main(string[] args)
    {
        // ...

        // From some dataSource, store some strings in a fancy PaginatedList.
        var myList = new PaginatedList<string>(dataSource);

        // Skip the first 20 strings and take the next 5 strings.
        var myData = OffsetGet(myList, 20, 5);

        // ...
    }
}

But how else can I implement OffsetGet(myList, skip, take) efficiently (that is, without making more than one Get(pageIndex, pageSize) call) for the case where the offset that I want to skip by is not divisible by the amount that I want to take?

Apologies if this question has already been answered somewhere or if the above code is too vague; this is my first question on StackOverflow, so please be gentle. =]


Solution

  • Note: there are significant issues with this answer (which is accepted so can't be deleted) - e.g. a skip of 28 with a take of 5. I don't have time to fix this, unfortunately.


    This partly depends on how much you want to balance code complexity vs data fetching efficiency.

    You could always succeed by fetching twice as much data as you need:

    int virtualTake = take * 2;
    int virtualSkip = skip / virtualTake;
    var bigList = Get(myList, virtualSkip, virtualTake);
    int offset = virtualSkip % take;
    var pruned = bigList.Skip(offset).Take(take);
    

    However, that will fetch more data than you need to in many cases. You could at least optimize for the case where skip is already a multiple of take, but there are other cases where you could improve.

    For example, if you want to skip 23 and take 5, the above will effectively skip 20 and take 10, then prune... but you could skip 21 and take 7 instead. Code based on the suggestion by astander in comments below:

    int newTake = take;
    while ((skip % newTake) + take > newTake)
    {
         newTake++;
    }
    var bigList = Get(myList, skip / newTake, newTake);
    int offset = skip % newTake;
    var pruned = bigList.Skip(offset).Take(take);