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



Question:

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!


Solution:1

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)


Solution:2

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]  


Solution:3

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]  


Solution:4

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)  


Solution:5

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.


Solution:6

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;      }  }  


Solution:7

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";  }  


Solution:8

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


Solution:9

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]  


Solution:10

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]  


Solution:11

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]


Solution:12

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


Solution:13

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>");  }  }  


Solution:14

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  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »