madhadron

Calculating pagination bounds for display

Status: Done
Confidence: Certain

Math in this page not rendering? See the fix

tl;dr: How do you calculate the limits for a pagination widget like “« 4 5 6 7 »”? Number the pages starting from 0. Then the first page to display startPagestartPage and one page after the last page to display endPageendPage (4 and 8 in the example above) are given by

startPage=[(currentPagewindow/2+1)Npageswindow]0 startPage = [ (currentPage - \lceil window/2 \rceil + 1) \ \downarrow \ Npages - window ] \uparrow 0

endPage=[(currentPage+window/2+1)window]Npages1 endPage = [ (currentPage + \lfloor window/2 \rfloor + 1) \ \uparrow \ window] \downarrow Npages - 1

where

That’s the short version. Now how do we derive that?

First, a warning. Don’t use page numbers as primitive objects unless you’ll only have one item per page. If you’re going to list multiple items per page, tracking page numbers instead of the first item to display on a page will make your code unbelievably complicated when you have to change the number of items displayed per page, or when you want to show a list of items beginning with a specific index, rather than a particular page.

So label your items from 0 to NN, and let currentItemcurrentItem be the index of the first item to be displayed on the current page. If each page is to show at most pageSizepageSize entries (“at most” because there may not be enough items to fill the last page to exactly pageSizepageSize), then currentPage=currentItem/pageSizecurrentPage = \lfloor currentItem / pageSize \rfloor, and the iith page will start at ipageSizei \cdot pageSize. There will be Npages=N/pageSizeNpages = \lceil N / pageSize \rceil pages in all.

Now let’s move on to deriving the expressions for the actual pagination range. What, precisely, is the problem? For a particular integer between 00 and NpagesNpages, we want to find a range of numbers of a fixed size which also fall in [0,Npages][0, Npages] and are as centered as possible around currentPagecurrentPage. As usual, we’ll describe the range as a half open interval by the first page in the range startPagestartPage and one after the last page in the range endPageendPage. For example, in “« 4 5 6 7 »”, startPage=4startPage = 4 and endPage=8endPage = 8. Let the size of the desired range be windowwindow.

endPagestartPage=windowendPage - startPage = window, but we need a second constraint to center currentPagecurrentPage as much as possible in this range. If windowwindow is odd, then we want the same number of entries on both sides of the current page number. For example, if we are on page 3 and window=5window = 5, We should display “« 1 2 3 4 5 »”. If windowwindow is even, we must make a choice: do we want to display more page numbers after the current one, or more before? I chose to show more numbers after on the theory that a user is more interested in stuff he has not yet reached. This constraint translates to

currentPagestartPage=endPagecurrentPage1(1window𝐦𝐨𝐝2)currentPage - startPage = endPage - currentPage - 1 - (1 - window \mathbf{mod} 2)

So, centering the current page in the control, currentPagestartPagecurrentPage - startPage should be one less than endPagecurrentPageendPage - currentPage since startPagestartPage is the first element and endPageendPage is one after the last element. So if windowwindow isn odd, currentPagestartPage=endPagecurrentPage1currentPage - startPage = endPage - currentPage - 1. If windowwindow is odd, since I chose to show more following pages, it is currentPagestartPage=endPagecurrentPage2currentPage - startPage = endPage - currentPage - 2. We can combine all these expressions to get

currentPagestartPage=endPagecurrentPage1(1window𝐦𝐨𝐝2)window=endPagestartPage\begin{eqnarray} currentPage - startPage & = & endPage - currentPage - 1 - (1 - window \mathbf{mod} 2) \\ window & = & endPage - startPage \end{eqnarray}

which we’ll solve for startPagestartPage and endPageendPage. We’ll be using three properties of the floor and ceiling functions in the calculations below, which I’ll summarize here:

x=x/2+x/2x/2=x/2(x𝐦𝐨𝐝2)/2x/2=x/2+(x𝐦𝐨𝐝2)/2\begin{eqnarray} x &=& \lfloor x/2 \rfloor + \lceil x/2 \rceil \\ \lfloor x/2 \rfloor &=& x/2 - (x\ \mathbf{mod}\ 2)/2 \\ \lceil x/2 \rceil &=& x/2 + (x\ \mathbf{mod}\ 2)/2 \\ \end{eqnarray}

We calculate thus:

currentPagestartPage=endPagecurrentPage1(1window𝐦𝐨𝐝2)currentPage - startPage = endPage - currentPage - 1 - (1 - window \mathbf{mod} 2) window=endPagestartPage\ \wedge\ window = endPage - startPage

\equiv { solve first equation for startPagestartPage and second equation for endPageendPage }

startPage=endPage2currentPage+2window𝐦𝐨𝐝2startPage = -endPage - 2 \cdot currentPage + 2 - window\ \mathbf{mod}\ 2 endPage=startPage+window\ \wedge\ endPage = startPage + window

\equiv { substitute endPageendPage for startPagestartPage in first equation }

startPage=startPagewindow2currentPage+2window𝐦𝐨𝐝2startPage = -startPage - window - 2 \cdot currentPage + 2 - window\ \mathbf{mod}\ 2 endPage=startPage+window\ \wedge\ endPage = startPage + window

\equiv { group startPagestartPage and simplify first equation }

startPage=currentPage(window/2+(window𝐦𝐨𝐝2)/2)+1startPage = currentPage - (window/2 + (window\ \mathbf{mod}\ 2)/2) + 1 endPage=startPage+window\ \wedge\ endPage = startPage + window

\equiv { x/2+(x𝐦𝐨𝐝2)/2=x/2x/2 + (x\ \mathbf{mod}\ 2)/2 = \lceil x/2 \rceil }

startPage=currentPagewindow/2+1startPage = currentPage - \lceil window/2 \rceil + 1 endPage=startPage+window\ \wedge\ endPage = startPage + window

\equiv { substitute for startPagestartPage in second equation }

startPage=currentPagewindow/2+1startPage = currentPage - \lceil window/2 \rceil + 1 endPage=currentPagewindow/2+1+window\ \wedge\ endPage = currentPage - \lceil window/2 \rceil + 1 + window

\equiv { x=x/2+x/2x = \lfloor x/2 \rfloor + \lceil x/2 \rceil }

startPage=currentPagewindow/2+1startPage = currentPage - \lceil window/2 \rceil + 1 endPage=currentPage+window/2+1\ \wedge\ endPage = currentPage + \lfloor window/2 \rfloor + 1

This works in the middle of the range. At the edges, we have to calculate a different window. The first page is 0 and can never be less. The last page Npages1Npages-1 and can never be more. At the left end, startPage=0startPage=0 and endPage=windowendPage=window. At the right end, startPage=NpageswindowstartPage = Npages - window and endPage=NpagesendPage = Npages. We augment the expressions for startPagestartPage and endPageendPage to handle these cases (note that the order of operations is important):

startPage=[(currentPagewindow/2+1)Npageswindow]0startPage = [ (currentPage - \lceil window/2 \rceil + 1) \ \downarrow \ Npages - window ] \uparrow 0 endPage=[(currentPage+window/2+1)window]Npages1endPage = [ (currentPage + \lfloor window/2 \rfloor + 1) \ \uparrow \ window] \downarrow Npages - 1

For quick reference, here is a Python function implementing this:

from math import ceil, floor

def bounds(current_page, n_pages, window):
    return (
        max(min(i - ceil(window / 2.0) + 1, npages - window), 0),
        min(max(i + floor(window / 2.0), window), npages-1)
    )