views:

859

answers:

14

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 gap 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 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!

A: 

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

Nick Presta
A: 

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]
seanizer
+2  A: 

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)
Svante
People actually use LISP? I thought that was just a fairy tale!
Nathan Taylor
You have no idea.
Svante
+3  A: 

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]
Mau
+7  A: 

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)

AKX
I think that the output is not correct in the (2 30) case. Also, subset size and the position of the current page in the subset are not configurable.
Svante
2 of 30 should be `[1][x][3][4][5]...[30]`
Nathan Taylor
that would be 6 pages, would it not?, 1, x, 3, 4, 5 and 30
Lasse V. Karlsen
The examples in the question make it clear that there should always be 5 surrounding pages, plus the end points if not already shown.
Svante
Aggresively edited to decrease char count and to add n=number of pages around the current one
Nas Banov
Clever edits, Nas Banov. :)
AKX
A: 

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]

Lowjacker
`ruby pager.rb 1 5 7` outputs `[120][2][3][4][5]...[7]`. I have ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-linux].
Svante
It only runs properly in Ruby 1.9, due to the character literal (`?x`) in the longest line. (It returns `120` on 1.8 and `'x'` on 1.9, hence the difference)
Lowjacker
+1  A: 

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.

Casey Hope
The complication is part of the challenge.
Svante
Producing a wrong answer elegantly is still producing a wrong answer.
Svante
A: 

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]
Nathan Taylor
Who uses 2-letter variable names in code golf?
Gabe
That's a good question. That saved me another 24 characters!
Nathan Taylor
@Mau yes it does. Page 12 falls in position 3 in a subset of 5- `[10][11][x][13][14]` and then `[15]` follows it without breaking sequence so no `...` is required. The same 6-in-a-row situation is duplicated by page 4 of 15: `[1][2][3][x][5][6]...[15]`. Test the other C# solution and you'll see the same output.
Nathan Taylor
+1  A: 

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;
    }
}
Ron Warholic
Gabe
I like, I like! Your imperative solution is a lot cleaner than mine.
Nathan Taylor
Thanks for the suggestions, down to 184.
Ron Warholic
+5  A: 

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]
Nabb
And it even really exists!
borisCallens
A: 

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

Swizec Teller
A: 

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

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
Nakilon
A: 

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";
}
xt.and.r