String of alphanumeric characters to a sorted list of comma-separated ranges

12

0

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

ABC321STPpJqZZr0

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

Rules

  • 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.

Andrew Cheong

Posted 2014-02-10T22:40:27.027

Reputation: 405

@FezVrasta - I intentionally wrote ", " to include the space, but we can leave your edit and let this comment serve as that indication. – Andrew Cheong – 2014-02-11T00:19:43.040

Why intentionally include GolfScript? Why not allow other languages, such as Befunge? – Justin – 2014-02-11T01:14:11.053

We can say all is fair game. I just won't be able to check most of them very easily. – Andrew Cheong – 2014-02-11T01:15:15.797

So is that a Code-Golf tag then? – VisioN – 2014-02-11T08:35:30.497

1@Chron - Good catch. A-B in my case but since there have already been many submissions, let's allow both ways. – Andrew Cheong – 2014-02-11T09:21:47.673

Answers

5

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.

Paul Prestidge

Posted 2014-02-10T22:40:27.027

Reputation: 2 390

+1 For one-liner. Very clever use of various methods; This is really brilliant. – daniero – 2014-02-11T04:12:09.723

1Note that gsub(/[]/i) is shorter than tr(''.upcase) by 2 characters. Moreover, scan(/.+/) -> split saves 5, and $><< instead of puts another one. – Howard – 2014-02-11T07:36:35.807

@Howard Great suggestions, thanks! – Paul Prestidge – 2014-02-11T09:15:15.447

2

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.

gggg

Posted 2014-02-10T22:40:27.027

Reputation: 1 715

1Thanks 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. – Andrew Cheong – 2014-02-10T23:10:04.787

2

GolfScript 57 54 52

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

Try it here.

The code first capitalizes everything:

{.95>32*-}%

Then gets unique characters and saves it in a variable:

.|:x

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

..{)}%&-x

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:

+$2/

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

Ben Reich

Posted 2014-02-10T22:40:27.027

Reputation: 1 577

1In the output, the ranges need to be separated by ', ' and not just ',' – Paul Prestidge – 2014-02-11T01:30:32.800

1Also .95>{32-}{}if -> .95>32*- saves 5 characters. – Howard – 2014-02-11T05:29:11.193

@Howard Great! I knew that part was suboptimal. – Ben Reich – 2014-02-11T05:47:13.390

1@Chron Fixed the space issue! – Ben Reich – 2014-02-11T05:47:56.407

2

C#, 221 bytes

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

Hand-E-Food

Posted 2014-02-10T22:40:27.027

Reputation: 7 912

2

C, 193

char*s="ABC321STPpJqZZr0";
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);
if(i-r>1)printf("-%c",i-1);r=f=0;}}

warrenm

Posted 2014-02-10T22:40:27.027

Reputation: 121

Can you add a small explanation? – Justin – 2014-02-11T06:42:21.807

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. – warrenm – 2014-02-11T07:25:09.240

2

Q, 94

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

tmartin

Posted 2014-02-10T22:40:27.027

Reputation: 3 917

1

Python 2.x, 304 - 16 = 288

This can surely be golfed further, all comments welcome!

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

ChristopheD

Posted 2014-02-10T22:40:27.027

Reputation: 1 599

1

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

draegtun

Posted 2014-02-10T22:40:27.027

Reputation: 1 592

1

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}

Usage

{.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"
Output
"0-3,A-C,J,P-T,Z"

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

nyi

Posted 2014-02-10T22:40:27.027

Reputation: 448

0

Tcl 8.0.5, 344 (360 bytes)

set a ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
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
= a ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
= 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 a ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
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.

Andrew Cheong

Posted 2014-02-10T22:40:27.027

Reputation: 405

1

Sometimes you can save bytes replacing while by time constructs. https://codegolf.stackexchange.com/a/126236/29325

– sergiol – 2017-09-16T13:53:27.393