Javascript ES6, 229 * 80% * 75% * 50% = 68.7 bytes === Test Input -- I'm using the following test data: var A1=[ 5,6,7, // => 5-7 # (a) group pages 2,3, // => 2-3,5-7 # (b) must be properly sorted -9,-8,-7, // => -10--8 # (c) allow negative numbers 29,30,31,32, // => 29-2 # (d) lower last digit implies increasing the 10s place 9,10,11,12, // => 9-11 # NOT 9-2 36,37,38,39,40,41,42,43,44,45,46,47, // => 36-47 # NOT 36-7 99,100,101,102, // => 99-102 # NOT 99-2 109,110,111,112, // => 109-2 # NOT 109-12 ], // more tests, not specified in the question A2=[ 120,124, // => 120,124 # (e) handle single pages ], A3=[ 135,136,135 // => 135-6 # (f) handle duplicates ]; Basic: 229 bytes --- This version satisfies the requirements of the question (a) with all bonuses (c,d,e), but hangs on single pages. It can also handle duplicates(f) F=(a)=>{var R=[],i=NaN,l,u;a.map(x=>R[1e3+x]=x);R.concat('').map(x=>(i!=i&&(l=x,u=l-1),i=0,u=(x+="")-u-1?l=console.log(l+'-'+(u>0?(l.length-u.length||(z=>{for(;l[i]==u[i];i++);})(),u.length-i-2||u-l>9||i++,u.slice(i)):u))||x:x))} F(A1.concat(A3)) --> -9--7 2-3 5-7 9-12 29-2 36-47 99-102 109-2 135-136 (The output above shows spaces instead of actual newlines for brevity) Single pages: 233 bytes -- This slightly longer version additionally satisfies (e) and displays single pages as a range with equal lower and upper limits G=(a)=>{var R=[],i=NaN,l,u;a.map(x=>R[1e3+x]=x);R.concat('').map(x=>(i!=i&&(l=x,u=l-1),i=0,u=(x+="")-u-1?l=console.log(l+'-'+(u-l&u>0?(l.length-u.length||(z=>{for(;l[i]==u[i];i++);})(),u.length-i-2||u-l>9||i++,u.slice(i)):u))||x:x))} G(A1.concat(A2,A3)) --> -9--7 2-3 5-7 9-12 29-2 36-47 99-102 109-2 120 124