Code Golf - Generate nearby page numbers based on the current page

1.2k views Asked by At

The challenge is to create an algorithm for generating a specifically-sized subset of numbers in a sequence based on the current position in that sequence.

While navigating through the many pages of content on a busy site like Stack Overflow or Digg it is often desirable to give the user a way to quickly jump to the first page, the last page or a specific page which is near the current page they are viewing.

Requirements

  • First and last page numbers are always displayed
  • The subset of page numbers will contain the current page number as well as page numbers before and/or after it (depending on current page)
  • The subset of page numbers will always be a fixed number of pages and can never exceed or fall short of that fixed number unless:
    • totalPages < fixedWidth
  • The position of the current page number in the subset is fixed unless:
    • 1 <= currentPage < (fixedWidth - defaultPostion) or
    • (totalPages - currentPage) < (fixedWidth - defaultPostion)
  • Output should indicate when there is a difference greater than 0 between the first page of data and the first page of the subset as well as between the last page of the subset and the last page of data. This indicator should appear at most once in either position.

If you can't picture this yet, take a look at your Stack Overflow profile under questions/answers. If you have more than 10 of either one, you should see paging links at the bottom which are generated in exactly this fashion. That, or scroll to the bottom of http://digg.com and observe their paging control.

Examples

All examples assume a subset size of 5 and the current page in position 3, but these should be configurable in your solution. ... indicates the gap between page numbers, [x] indicates the current page.


Current Page: 1 of 30

Output: [x][2][3][4][5]...[30]


Current Page: 2 of 30

Output: [1][x][3][4][5]...[30]


Current Page: 13 of 30

Output: [1]...[11][12][x][14][15]...[30]


Current Page: 27 of 30

Output: [1]...[25][26][x][28][29][30]


Current Page: 30 of 30

Output: [1]...[26][27][28][29][x]


Current Page: 3 of 6

Output: [1][2][x][4][5][6]


Current Page: 4 of 7

Output: [1][2][3][x][5][6][7]


Additional Clarifications

  • First and last pages do not count toward numberOfPages unless they are sequentially part of numberOfPages as in [1][x][3][4][5]...[30] or [1]...[26][27][28][x][30], but not in [1]...[8][9][x][11][12]...[30]
  • No gap indicator should be included if the distance between either end of the subset and the first or last page is less than 1. Thus, it is possible to have a non-breaking sequence of pages up to fixedWidth + 2 as in [1][2][3][x][5][6]...[15] or [1][2][3][x][5][6][7]

Solutions in any and all languages are welcome.

Good luck!

14

There are 14 answers

0
Nick Presta On

Python - 321 characters

I'm assuming you're typing in the current page and total pages on the command line (stdin):

import sys
p=sys.stdout.write
c,t=raw_input().split()
c,t=int(c),int(t)
r=range(1,t+1)
l=len(r)
p("[1]")
if c>7:
 p("...")
for n in r[c-3:c+2]:
 if n==1:continue
 if n-t==-5 and l>7:continue
 if c==n:n="X"
 p("[%s]"%n)
if l<7:
 for n in range(2,6):
  if c==n:n="X"
  p("[%s]"%n)
if r[c+2]<t and l>6:
 p("...")
p("[%d]"%t)

Not really golfed (just short names) so I expect the best solution to be at least half this length.

Example

python pag.py
3 30
[1][2][X][4][5]...[30]

Edit: I realize this fails for thing like "2 4" or "2 2" - it assumes that are at least 6 pages. shrug

0
Sean Patrick Floyd On

Groovy: 242 232 chars, supports configurable group length

Call syntax: Paging(currentOffset, totalWidth, groupSize)

def(c,t,g)=args.collect{it.toInteger()};def p,s=Math.max(c-(g/2).toInteger(),1);p='['+((c==1?'x':1)+(s>2?']...':']'));(s..Math.min(s+g-1,t)).each{if(it>1&&it<t)p+='['+(c==it?'x':it)+']'};print p+(t>s+g?'...[':'[')+(t==c?'x':t)+']';

Readable version:

def (c,t,g) = args.collect{it.toInteger()};
def p,s = Math.max(c - (g/2).toInteger(), 1);
p = '['+((c==1?'x':1)+(s>2?']...':']'));
(s .. Math.min(s+g-1,t)).each{
    if(it > 1 && it < t)
        p += '[' + (c == it ? 'x' : it) + ']'
};
print p + (t > s + g ? '...[' : '[') + (t==c ? 'x' : t) + ']';

Calling it like this:

Paging ([1, 20, 5])
println '';
Paging ([10, 20, 5])
println '';
Paging ([20, 20, 5])
println '';
Paging ([7, 17, 3])
println '';
Paging ([2, 228, 3])
println '';
Paging ([2, 5, 3])
println '';
Paging ([1, 5, 5])

produces these results:

[x][2][3][4][5]...[20]
[1]...[8][9][x][11][12]...[20]
[1]...[18][19][x]
[1]...[6][x][8]...[17]
[1][x][3]...[228]
[1][x][3]...[5]
[x][2][3][4][5]
1
Svante On

Common Lisp: 262 significant characters

(defun [(n)(format t"[~a]"n))(defun p(c m &key(s 5)(p 2))(let((l(max(min(- c p)(- m s -1))1))(r(min(max(+ c(- p)s -1)s)m)))(when(> l 1)([ 1))(when(> l 2)(princ"..."))(loop for n from l to r do([ (if(= n c)#\x n)))(when(< r(1- m))(princ"..."))(when(< r m)([ m))))

Uncompressed:

(defun print[] (n)
  (format t "[~a]" n))

(defun page-bar (current max &key (subset-size 5) (current-position 2))
  (let ((left (max (min (- current current-position)
                        (- max subset-size -1))
                   1))
        (right (min (max (+ current (- current-position) subset-size -1)
                         subset-size)
                    max)))
    (when (> left 1) (print[] 1))
    (when (> left 2) (princ "..."))
    (loop for p from left upto right
          do (print[] (if (= p current) #\x p)))
    (when (< right (1- max)) (princ "..."))
    (when (< right max) (print[] max))))

Testing:

CL-USER> (mapc (lambda (n) (p n 7) (format t "~%")) '(1 2 3 4 5 6 7))
[x][2][3][4][5]...[7]
[1][x][3][4][5]...[7]
[1][2][x][4][5]...[7]
[1][2][3][x][5][6][7]
[1]...[3][4][x][6][7]
[1]...[3][4][5][x][7]
[1]...[3][4][5][6][x]
(1 2 3 4 5 6 7)
CL-USER> (p 1 1)
[x]
NIL
CL-USER> (p 1 2)
[x][2]
NIL
CL-USER> (p 0 0)
NIL
CL-USER> (p 0 1)
[1]
NIL
CL-USER> (p 0 30)
[1][2][3][4][5]...[30]
NIL
CL-USER> (p 31 30)
[1]...[26][27][28][29][30]
NIL

The subset size and the position of the current page in that subset can be given in optional parameters (:current-position is zero-based within the subset, naturally):

CL-USER> (page-bar 8 15 :subset-size 6 :current-position 5)
[1]...[3][4][5][6][7][x]...[15]
NIL

EDIT: The call in the compressed version would be:

CL-USER> (p 8 15 :s 6 :p 5)
0
Mau On

F# ! - 233 significant characters.

All options supported and within specs.

Program:

let P c b n f m s =
    let p = b/2
    let u = max 1 (if n-b <= c-p   then n-b+1 else max 1 (c-p))
    let v = min n (if b   >= c+p-1 then b     else min n (c+p))
    let P = printf
    let C c a n = if c then P a n
    C (u > 1)  f   1
    C (u = 3)  f   2
    C (u > 3) "%s" s
    let I = Seq.iter (P f)
    I {u .. c-1}
    P "%s" m
    I {c+1 .. v}
    C (n - 2 > v) "%s" s
    C (v = n - 2)  f   (n-1)
    C (n > v)      f   n

Test:

for p in 1..6 do
    P p 5 30 "[%d]" "[x]" "..."
    printfn ""

for p in 25..30 do
    P p 5 30 "[%d]" "[x]" "..."
    printfn ""

Output:

[x][2][3][4][5]...[30]
[1][x][3][4][5]...[30]
[1][2][x][4][5]...[30]
[1][2][3][x][5]...[30]
[1][2][3][4][x][6][7]...[30]
[1]...[4][5][x][7][8]...[30]

[1]...[23][24][x][26][27]...[30]
[1]...[24][25][x][27][28][29][30]
[1]...[26][x][28][29][30]
[1]...[26][27][x][29][30]
[1]...[26][27][28][x][30]
[1]...[26][27][28][29][x]
5
AKX On

Python - 156 182 140 characters

f=lambda c,m,n:'...'.join(''.join((' ','[%s]'%(p,'x')[p==c])[min(m-n,c-1-n/2)<p<max(n+1,c+1+n/2)or p in(1,m)]for p in range(1,m+1)).split())

And testing against examples in OP:

for c, m, expect in (
    (1,  30, "[x][2][3][4][5]...[30]"),
    (2,  30, "[1][x][3][4][5]...[30]"),
    (13, 30, "[1]...[11][12][x][14][15]...[30]"),
    (27, 30, "[1]...[25][26][x][28][29][30]"),
    (30, 30, "[1]...[26][27][28][29][x]"),
    (3,  6,  "[1][2][x][4][5][6]"),
    (4,  7,  "[1][2][3][x][5][6][7]"),
):
    output = f(c, m, 5)
    print "%3d %3d %-40s : %s" % (c, m, output, output == expect)

Thanks for the comments. :)

PS. heavily edited to decrease char count and to add n=number of pages around the current one (m is max number of pages and c is the current page no)

2
AudioBubble On

Ruby 1.9 - 197 characters

p,s,t=$*.map &:to_i
a=p-s/2
e=a+s-1
a<1&&(e+=1-a;a=1)
e>t&&(a-=e-t;e=t)
s>=t&&(a=1;e=t)
m=(a..e).map{|n|"[#{n==p ??x:n}]"}.join
a>2&&m='...'+m
a>1&&m='[1]'+m
e<t-1&&m<<'...'
e<t&&m<<"[#{t}]"
puts m

Usage: ruby pager.rb [position] [sampleSize] [totalWidth]

2
Casey Chu On

PHP, 234 chars

function pages($t,$c,$s=5){$m=ceil($s/2);$p=range(1,$t);$p[$c-1]='x';$a=array();return preg_replace('~(\[('.implode('|',array_merge($c-$m<2?$a:range(2,$c-$m),$t-1<$c+$m?$a:range($c+$m,$t-1))).')\])+~','...','['.implode('][',$p).']');}

(Sort of) unminified:

function pages($max, $current, $subset=5) {
    $m = ceil($subset / 2); // amount to go in each direction
    $arr = range(1, $max); // array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    $arr[$current-1] = 'x'; // array(1, 2, 3, 4, x, 6, 7, 8, 9, 10)

    // replace ~(\[(2|8|9)\])+~ with ...
    $pattern = '~(\[(' . implode('|', array_merge($current-$m >= 2 ? range(2, $current-$m) : array(), $max-1 >= $current+$m ? range($current+$m, $max-1): array())) . ')\])+~';
    return preg_replace($pattern, '...', '['.implode('][',$arr).']');
}

This doesn't follow the spec exactly ([1][x][3][4]...[30] instead of [1][x][3][4][5]...[30]), but it would become a lot less elegant accounting for that.

1
Nathan Taylor On

C# 278 Characters


Program

void Pages(int c,int t,int w){
    int p=(w/2)+1;
    int b=c-p;
    int f=c+(w-p);
    if(b<0){
        f+=b*-1;
    }else if(f>t){
        b-=f-t;
        f=t;
    }
    for(int i=1;i<=t;i++){
        if(t<=w||(i==1||i==t)||(i>b&&i<=f))
            Console.Write(i==c?"[X]":"[{0}]",i);
        else if(t>w&&(i==b||i==f+1))
            Console.Write("...");
    }
}

Test

for(int i=1;i<=5;i++) {
    Pages(i,5,5);
    Console.WriteLine();
}

for(int i=1;i<=15;i++) {
    Pages(i,15,5);
    Console.WriteLine();
}

Output

[X][2][3][4][5]
[1][X][3][4][5]
[1][2][X][4][5]
[1][2][3][X][5]
[1][2][3][4][X]

[X][2][3][4][5]...[15]
[1][X][3][4][5]...[15]
[1][2][X][4][5]...[15]
[1][2][3][X][5][6]...[15]
[1]...[3][4][X][6][7]...[15]
[1]...[4][5][X][7][8]...[15]
[1]...[5][6][X][8][9]...[15]
[1]...[6][7][X][9][10]...[15]
[1]...[7][8][X][10][11]...[15]
[1]...[8][9][X][11][12]...[15]
[1]...[9][10][X][12][13]...[15]
[1]...[10][11][X][13][14][15]
[1]...[11][12][X][14][15]
[1]...[11][12][13][X][15]
[1]...[11][12][13][14][X]
3
Ron Warholic On

C#, 240/195 184 chars

Similar to the other C# answer but with some nasty side-effect filled LINQ. I would imagine this could be somewhat shorter.

void Pages(int p,int t,int s) {
  int h=s/2,l=0;
  foreach(var c in Enumerable.Range(1,t).Where(x=>x==1||x==t||(p+h<s&&x<=s)||(p-h>t-s&&x>t-s)||(x>=p-h&&x<=p+h)).Select(x=>{Console.Write((x-l>1?"...":"")+(x==p?"[X]":"["+x+"]"));l=x;return x;}));
}

Edit:

Turns out the imperative version is shorter by a good margin (195 184 characters):

void Pages(int p,int t,int s){
  int h=s/2,l=0,i=1;
  for(;i<=t;i++)
    if(i==1||i==t||p+h<s&&i<=s||p-h>t-s&&i>t-s||i>=p-h&&i<=p+h){
      Console.Write((i-l>1?"...":"")+(i==p?"[X]":"["+i+"]"));
      l=i;
    }
}
0
Nabb On

GolfScript - 89 80 78 chars

~:&;\:C;:T,{-1%[T&-T)C-:C&2/-(]$0=:|1>{.1<\|>}*}2*]${{)]`[C]`/'[x]'*}%}%'...'*

Sample I/O:

$ echo "27 30 5"|golfscript page_numbers.gs
[1]...[25][26][x][28][29][30]

Output for all page numbers takes 83 chars (minor modifications to the main body).

~:&;:T,{:C;T,{-1%[T&-T(C-:C&2/-]$0=:|1>{.1<\|>}*}2*]${{)]`[C)]`/'[x]'*}%}%'...'*n}

Sample I/O:

$ echo "7 5"|golfscript page_numbers.gs
[x][2][3][4][5]...[7]
[1][x][3][4][5]...[7]
[1][2][x][4][5]...[7]
[1][2][3][x][5][6][7]
[1]...[3][4][x][6][7]
[1]...[3][4][5][x][7]
[1]...[3][4][5][6][x]

$ echo "7 3"|golfscript page_numbers.gs
[x][2][3]...[7]
[1][x][3]...[7]
[1][2][x][4]...[7]
[1]...[3][x][5]...[7]
[1]...[4][x][6][7]
[1]...[5][x][7]
[1]...[5][6][x]
0
Swizec Teller On

Python - 334 characters - complete functionality

I realize a shorter answer has already been posted, but that one doesn't support configurable width and position in the subset of pages. Mine does.

def paginator(c, n, w, o):
    b = range(c-w/2+o,c+w/2+1+o)
    b = [e+abs(b[0])+1 for e in b]if b[0]<=0 else[e-abs(n-b[w-1])for e in b]if b[w-1]>n else b
    p = ([]if 1 in b else[1])+b+([]if n in b else[n])
    return ''.join(('...'if p[i]-p[i-1]!=1 and i>0 and i<len(p)else'')+'[%d]'%p[i]if p[i]!=c else'[x]'for i in range(len(p)))

And here are the tests that all pass

if __name__ == '__main__':
    for current, n, width, offset, expect in (
        (1,  30, 5, 0, "[x][2][3][4][5]...[30]"),
        (2,  30, 5, 0, "[1][x][3][4][5]...[30]"),
        (13, 30, 5, 0, "[1]...[11][12][x][14][15]...[30]"),
        (13, 30, 5, 1, "[1]...[12][x][14][15][16]...[30]"),
        (13, 30, 5, -1, "[1]...[10][11][12][x][14]...[30]"),
        (27, 30, 5, 0, "[1]...[25][26][x][28][29][30]"),
        (30, 30, 5, 0, "[1]...[26][27][28][29][x]"),
        (30, 30, 5, 1, "[1]...[26][27][28][29][x]"),
        (3,  6, 5, 0,  "[1][2][x][4][5][6]"),
        (3,  6, 5, -1,  "[1][2][x][4][5][6]"),
        (3,  6, 5, 1,  "[1][2][x][4][5][6]"),
        (4,  7, 5, 0,  "[1][2][3][x][5][6][7]"),
        ):
        output = paginator(current, n, width, offset)
        print "%3d %3d %3d %3d %-40s : %s" % (current, n, width, offset, output, output == expect)
        print ''

This is my first code-golf, awesome stuff, going to do a lot more from now on :P

0
pop850 On

Javascript - 398 393 characters


Working Function

v(j, o, l), where:

  • j is the page number
  • o is the total number of pages
  • l is the number of pages to display (subset size)

v(10, 30, 5) returns: [1]...[8][9][x][11][12]…[30]

function v(j,o,l){function k(q){return q.length}function y(n,m){t=[];while(n<=m){t.push(n);n++}return t}r=y(1,j-1);g=y(j+1,o);b=k(r);a=k(g);c=l/2;(b>l/2&&a>=c)?r=r.splice(-l/2):((a<=c)?r=r.splice(-l+a+1):0);b=k(r);g=g.slice(0,l-1-b);a=k(g);r.push("x");g[a-1]==o-1?g.push(o):0;r[0]==2?r.unshift(1):0;r=r.concat(g);return(r[0]>2?"[1]...":"")+"["+r.join("][")+"]"+(g[k(g)-1]<o-1?"...["+o+"]":"")}


Uncompressed version

function run(cp, tp, l) {
function y(n,m){t=[];while(n<=m){t.push(n);n++}return t};
var before=y(1, cp-1);

var after=y(cp+1, tp);

var b=before.length;
var a=after.length;

var c=Math.floor(l/2);

if (b>l/2 && a>=c) {
    before=before.splice(-l/2);

} else if (a<=c) {
    before=before.splice(-(l-a)+1);
}
b=before.length;

after=after.slice(0, l-1-b);
a=after.length

before.push("x");

if (after[a-1]==tp-1)
    after.push(tp);

if (before[0]==2)
    before.unshift(1);

before=before.concat(after);

// Add bounds to either side
var pre=["",""];
if (before[0]>2) pre[0]="[1]...";
if (after[after.length-1]<tp-1) pre[1]="...["+tp+"]";

return pre[0]+"["+before.join("][")+"]"+pre[1];
}


A simple test function

function testValues() {
var ts=[1, 30, "[x][2][3][4][5]...[30]",
        2, 30, "[1][x][3][4][5]...[30]",
        13, 30, "[1]...[11][12][x][14][15]...[30]",
        27, 30, "[1]...[25][26][x][28][29][30]",
        30, 30, "[1]...[26][27][28][29][x]",
        3, 6, "[1][2][x][4][5][6]",
        4, 7, "[1][2][3][x][5][6][7]"];
for (var i=0; i<ts.length; i+=3) {
    var rr=v(ts[i], ts[i+1], 5);
    document.write(ts[i]+" of "+ts[i+1]+":  "+rr+" |Correct-> "+ts[i+2]+"<br>");
    ts[i+2]==rr ? document.write("<span style='color:green'>Check!</span>") : document.write("<span style='color:red'>Fail</span>");
    document.write("<br><br>");
}
}
0
Nakilon On

Ruby 1.9.1 — 114

for x,n,w in [[1,30,5],[2,30,5],[13,30,5],[27,30,5],[30,30,5],[3,6,5],[4,7,5]]

    puts (1..n).map{|i|i>[-w/2+x,n-w].min&&i<=[x+w/2,w].max||i==1||i==n ?"[#{i==x ?'x':i}]":'-'}.join.gsub(/-+/,'...')

end
0
Sayan Malakshinov On

Perl, 92 chars

$_=join'',map{$_==1||$_==$n||abs($_-$x)<=$a?$_==$x?'[x]':"[$_]":'_'}(1..$n);s/_+/.../g;print

Full test:

@i=(
 [1,30,2],
 [2,30,2],
 [13,30,2],
 [27,30,2],
 [30,30,2],
 [3,6,2],
 [4,7,2]
);

for$r(@i)
{
($x,$n,$a)=@$r;

$_=join'',map{$_==1||$_==$n||abs($_-$x)<=$a?$_==$x?'[x]':"[$_]":'_'}(1..$n);s/_+/.../g;print
;print"\n";
}