Code Golf - Generate nearby page numbers based on the current page
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 ofnumberOfPages
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-breakin开发者_JAVA技巧g 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!
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)
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]
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]
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)
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.
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;
}
}
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";
}
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
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]
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]
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]
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
Javascript - 398 393 characters
Working Function
v(j, o, l)
, where:
j
is the page numbero
is the total number of pagesl
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>");
}
}
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
精彩评论