Given a string of unsorted alphanumeric characters, e.g.


output a ", "-separated list of character ranges, sorted by ASCII value, ignoring case and removing duplicates (i.e. outputting only uppercase and numeric characters), e.g.

0-3, A-C, J, P-T, Z


  • The length of your program is your base score, as usual.
  • You must initialize (hardcode) the above example within your program, but you may discount the length of that example from your program length, e.g. for char* s="ABC321STPpJqZZr0"; you may discount 16 chars, the other 11 chars counting toward your program length.

Bonus (+50 bounty)

  • As this was a real problem encountered by my coworker today, needing to be written in Tcl 8.0.5 (an ancient version, lacking many of the latest Tcl built-ins), I'll award 50 points to whomever writes the shortest Tcl 8.0.5 solution, if there are at least 2 valid submissions in Tcl 8.0.5.
  • \$\begingroup\$ @FezVrasta - I intentionally wrote ", " to include the space, but we can leave your edit and let this comment serve as that indication. \$\endgroup\$ Commented Feb 11, 2014 at 0:19
  • \$\begingroup\$ Why intentionally include GolfScript? Why not allow other languages, such as Befunge? \$\endgroup\$
    – Justin
    Commented Feb 11, 2014 at 1:14
  • \$\begingroup\$ We can say all is fair game. I just won't be able to check most of them very easily. \$\endgroup\$ Commented Feb 11, 2014 at 1:15
  • \$\begingroup\$ So is that a Code-Golf tag then? \$\endgroup\$
    – VisioN
    Commented Feb 11, 2014 at 8:35
  • 1
    \$\begingroup\$ @Chron - Good catch. A-B in my case but since there have already been many submissions, let's allow both ways. \$\endgroup\$ Commented Feb 11, 2014 at 9:21

10 Answers 10


Ruby, 87-16=71

EDIT: Had to add some characters so that two-character ranges are displayed correctly. Also using ?[ instead of ?Z to fix a bug with ranges ending in Z.

$><<[*?0..?[].join.gsub(/[^ABC321STPpJqZZr0]/i,$/).gsub(/\B.+\B/,?-).scan(/.-.|./)*', '

You can see the Ideone run here.

  • \$\begingroup\$ +1 For one-liner. Very clever use of various methods; This is really brilliant. \$\endgroup\$
    – daniero
    Commented Feb 11, 2014 at 4:12
  • 1
    \$\begingroup\$ Note that gsub(/[]/i) is shorter than tr(''.upcase) by 2 characters. Moreover, scan(/.+/) -> split saves 5, and $><< instead of puts another one. \$\endgroup\$
    – Howard
    Commented Feb 11, 2014 at 7:36
  • \$\begingroup\$ @Howard Great suggestions, thanks! \$\endgroup\$ Commented Feb 11, 2014 at 9:15

Julia, 131

julia> l=sort(unique(uppercase("ABC321STPpJqZZr0")))
julia> prod([!(c+1 in l)?"$c"*(c==l[end]?"":", "):!(c-1 in l)?"$c":(c+1 in l)&&!(c+2 in l)?"-":"" for c in l])

"0-3, A-C, J, P-T, Z"

Not supported by Ideone.com, and will probably be crushed anyway.

  • 1
    \$\begingroup\$ Thanks anyway! The Ideone.com constraint was only so I could test it but I suppose I can trust the integrity of golfers and remove that rule. +1, anyway. \$\endgroup\$ Commented Feb 10, 2014 at 23:10

C#, 221 bytes

class P{
    static void Main(){
        var s="ABC321STPpJqZZr0";
        var l=new int[257];
        foreach(int c in s.ToUpper())
        var r="";
        for(int i=0;i<255;){
                r+=", "+(char)i;
            else if(l[i+1]-l[i]<0)

C, 193

int c[99];memset(c,0,396);while(*s){++c[toupper(*s++)];}for(int i=0,f=1,r=0;
i<=99;++i){if(!r&&c[i])r=i;if(r&&!c[i]){if(!f)printf(", ");putchar(r);
  • \$\begingroup\$ Can you add a small explanation? \$\endgroup\$
    – Justin
    Commented Feb 11, 2014 at 6:42
  • \$\begingroup\$ Iterate over the string, accumulating a count of instances of each alphanumeric character. Then, iterate over all alphanumeric characters in alphabetical order, writing out the start of each compact range and, if appropriate, a dash followed by the end of the range. If this is not the first range that's been written, add the comma-space separator. Code must be embedded in a main() function with appropriate headers (stdio, string, ctypes) included, so I kinda cheated there. \$\endgroup\$
    – warrenm
    Commented Feb 11, 2014 at 7:25

GolfScript 57 54 52

 {.95>32*-}%.|:x..{(}%&-x..{)}%&-+$2/{.|'-'*}%', '*

Try it here.

The code first capitalizes everything:


Then gets unique characters and saves it in a variable:


Then, we get the characters whose direct predecessors are not in the string (so that they are the beginning part of a range):


We similarly get the ends of ranges with x..{)}%&-.

Now actually form the ranges by concatenating the lists, sorting, and splitting into groups of 2:


The rest is just formatting, using * as string join.

  • 1
    \$\begingroup\$ In the output, the ranges need to be separated by ', ' and not just ',' \$\endgroup\$ Commented Feb 11, 2014 at 1:30
  • 1
    \$\begingroup\$ Also .95>{32-}{}if -> .95>32*- saves 5 characters. \$\endgroup\$
    – Howard
    Commented Feb 11, 2014 at 5:29
  • \$\begingroup\$ @Howard Great! I knew that part was suboptimal. \$\endgroup\$
    – Ben Reich
    Commented Feb 11, 2014 at 5:47
  • 1
    \$\begingroup\$ @Chron Fixed the space issue! \$\endgroup\$
    – Ben Reich
    Commented Feb 11, 2014 at 5:47

Q, 94

{","sv(,/){{"-"sv(?) -1 1#\:x}'[cut[;a]0,1_(&)1<(-':)"i"$'a:asc upper[x]inter y]}[x]'[.Q`n`A]}

Python 2.x, 304 - 16 = 288

This can surely be golfed further, all comments welcome!

for c in"ABC321STPpJqZZr0".lower():e["0123456789".find(c)]=f["abcdefghijklmnopqrstuvwxyz".find(c)]=c
def h(j):
 for e in j:
  if e:
   if not l:k=i;l=1
  elif l:l=g.append((k,i-1))
 print", ".join([j[m],j[m]+"-"+j[n]][n-m>1]for m,n in g)

Rebol (218 - 16 = 202)

m: s: sort uppercase unique"ABC321STPpJqZZr0"i: :to-integer f: does[either 1 = length? x: copy/part m s[x][rejoin[x/1"-"last x]]]while[not tail? s: next s][if(1 + i pick back s 1)!=(i s/1)[prin join f", "m: s]]print f

Non-minified version:

m: s: sort uppercase unique "ABC321STPpJqZZr0"
i: :to-integer

f: does [
    either 1 = length? x: copy/part m s [x] [rejoin [x/1 "-" last x]]

while [not tail? s: next s][
    if (1 + i pick back s 1) != (i s/1) [
        prin join f ", "
        m: s

print f

q [116 chars]

{.a:();{m:6h$x;.a:.a,$[m[1]=1+m[0];45;m[0],44,m 1];1_x}/[x:asc distinct upper x];p where differ 6h$p:-3_10h$x[0],.a}


{.a:();{m:6h$x;.a:.a,$[m[1]=1+m[0];45;m[0],44,m 1];1_x}/[x:asc distinct upper x];p where differ 6h$p:-3_10h$x[0],.a}"ABC321STPpJqZZr0"

There is a scope of saving chars, i'll try some other method and post it.


Tcl 8.0.5, 344 (360 bytes)

set s string
set x [join [lsort [split [$s toupper ABC321STPpJqZZr0] ""]] ""]
regsub -all (.)\\1+ $x \\1 x
set i 36
while {[incr i -1]} {set j -1
while {$i+[incr j]<36} {set y [$s range $a $j [expr $i+$j]]
regsub $y $x [$s index $y 0]-[$s index $y end],\  x}}
while {[regsub -all {(\w)(\w)} $x {\1, \2} x]} {}
puts $x

Tcl 8.0.5, 340 (356 bytes)

Tinkering with the rename command yielded some fun tricks! I've documented them in another thread.

rename rename &
& set =
& regsub R
& string S
& while W
= x [lsort [split [S toupper ABC321STPpJqZZr0] ""]]
R -all {(.) \1+| } $x \\1 x
= i 36
W {[incr i -1]} {= j -1
W {$i+[incr j]<36} {= y [S range $a $j [expr $i+$j]]
R $y $x [S index $y 0]-[S index $y end],\  x}}
W {[R -all {(\w)(\w)} $x {\1, \2} x]} {}
puts $x

Tcl 8.0.5, 332 (348 bytes) [Unstable—depends on $PATH]

info script ""
set tcl_interactive 1
set x [lso [sp [st toupper ABC321STPpJqZZr0] ""]]
regs -all {(.) \1+| } $x \\1 x
set i 36
wh {[inc i -1]} {set j -1
wh {$i+[inc j]<36} {set y [st range $a $j [exp $i+$j]]
regs $y $x [st index $y 0]-[st index $y end],\  x}}
wh {[regs {(\w)(\w)} $x {\1, \2} x]} {}
pu $x

Credit to @JohannesKuhn for the interactive trick.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.