Javascript, 212 * 80% * 75% * 50% = 63.6 bytes
Test Input
I'm using the following test array:
var A=[
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,
// => 26-37 # NOT 26-7
99,100,101,102, // => 29-2 # NOT 99-2
109,110,111,112, // => 29-2 # NOT 109-12
// more tests, not specified in the question
// 120,124, // => 120,124 # (e) handle single pages
// 135,136,135 // => 135-6 # (f) handle duplicates
];
Basic: 212 bytes
This version satisfies the requirements of the test (a) with all bonuses (c,d,e), but hangs on single pages and garbles duplicates.
F=(a)=>{var i=a.sort((a,b)=>a-b),l=a[0],u=l-1;a.concat('').map(x=>u=(x+="")-u-1?l=console.log(l+'-'+(u<0?u:(i=0,l.length-u.length||()=>{for(;l[i]==u[i];i++);}(),u.length-i-2||u[i]-l[i]-1||i++,u.slice(i))))||x:x)}
Single pages: 218 bytes
This slightly longer version additionally satisfies (e), and shows single pages as single numbers (e.g. 3-5, 7, 9-12).
G=(a)=>{var i=a.sort((a,b)=>a-b),l=a[0],u=l-1;a.concat('').map(x=>u=(x+="")-u-1?l=console.log(l-u?l+'-'+(u<0?u:(i=0,l.length-u.length||()=>{for(;l[i]==u[i];i++);}(),u.length-i-2||u[i]-l[i]-1||i++,u.slice(i))):l)||x:x)}
Filter duplicates: 243 bytes
Another, somewhat longer version, which also satisfies (f). I suspect that .filter
can be avoided and handling of duplicates handled in the main loop in some shorter way.
H=(a)=>{var i=a.sort((a,b)=>a-b),l=a[0],u=l-1;a.filter((x,i)=>a[i+1]!=x).concat('').map(x=>u=(x+="")-u-1?l=console.log(l-u?l+'-'+(u<0?u:(i=0,l.length-u.length||()=>{for(;l[i]==u[i];i++);}(),u.length-i-2||u[i]-l[i]-1||i++,u.slice(i))):l)||x:x)}