bfdi533 has asked for the wisdom of the Perl Monks concerning the following question:

I have need of code to split a list as follows:

1-5,6a-e,7,10-13

into a list broken out as follows:

1
2
3
4
5
6a
6b
6c
6e
7
10
11
12
13

I have no idea how to do this other than with split to find the commas but that does not take care of the dashes.

Thanks,
Ed

Replies are listed 'Best First'.
Re: need help to split list
by BrowserUk (Patriarch) on Oct 24, 2003 at 21:54 UTC

    As a one-liner

    @a=map{m[(\d+)([a-z]?)-(\d+|[a-z])]and$2?(map{$1.$_}$2..$3):($1..$3)}s +plit',',$s; print "@a"; 1 2 3 4 5 6a 6b 6c 6d 6e 10 11 12 13

    Update: The above completely misses the 7!

    Corrected and expanded for a little more clarity

    print map{ m[ (\d+) ([a-z]?) -? (\d+|[a-z])? ]x and $2 ? ( map{ $1 . $_ } $2 .. $3 ) : $3 ? ( $1 .. $3 ) : ( $1 ) }split ',', $s; 1 2 3 4 5 6a 6b 6c 6d 6e 7 10 11 12 13

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!

      Slick solution, but it skips 7. Here's a fixed (and slightly more readable, using if instead of the ternary operator) version.

      #!/usr/bin/perl -l my $s = '1-5,6a-e,7,10-13'; my @a = map{ if (m[ (\d+) ([a-z]?) - (\d+|[a-z]) ]x) { if ($2) { map{ $1 . $_ } $2 .. $3; } else { ( $1 .. $3 ); } } else { $_; } } split ',', $s; print join ' ', @a;

      ... and the logical conclusion:

      @a=map/(\d+)(\D*)-/?$2?map$1.$_,$2..$':$1.$2..$':$_,split',',$s;
Re: need help to split list
by gjb (Vicar) on Oct 24, 2003 at 22:02 UTC

    A bit longer than BrowserUK's approach, but probably easier to understand and a bit more robust:

    #!/usr/bin/perl use strict; my $list = '1-5,6a-e,7,10-13'; my @list = split(/\s*,\s*/, $list); print join("\n", expand($_)), "\n" foreach @list; sub expand { my $str = shift; if ($str =~ /^(\d+)\-(\d+)$/) { return ($1..$2); } elsif ($str =~ /^(\d+)([a-z]+)\-([a-z]+)$/) { return map {$1.$_} ($2..$3); } elsif ($str =~ /^\d+$/) { return $str; } else {f die("wrong format"); } }
    Note that it doesn't check whether the ranges are valid, e.g. whether $1 <= $2, but that's trivial to add.

    Hope this helps, -gjb-

      Quite good and very readable! Thanks.
Re: need help to split list
by chromatic (Archbishop) on Oct 24, 2003 at 22:00 UTC

    This was a fun one. Here's a non-golfy solution I quite like, despite its assumptions.

    #!/usr/bin/perl -w use strict; use Test::More tests => 1; my @list = qw( 1 2 3 4 5 6a 6b 6c 6d 6e 7 10 11 12 13 ); chomp( my $range = <DATA> ); my @result = split_list( $range ); is_deeply( \@result, \@list ); sub split_list { my $range = shift; my @list; for my $r ( split( /,/, $range ) ) { my $iter = get_points( $r ); while (my $next = $iter->()) { push @list, $next; } } return @list; } sub get_points { my $range = shift; my ($start, $end) = split( /\-/, $range ); return iter_int($start, $end) unless $start =~ /\D/; my ($number, $letter) = $start =~ /^(\d+)(\D)$/; return iter_str( $number, $letter, $number . $end ); } sub iter_int { my ($start, $end) = @_; $end ||= $start; return sub { return if $start > $end; return $start++; }; } sub iter_str { my ($start, $letter, $end) = @_; my $last = ''; return sub { return if $last eq $end; my $next = $start . $letter; $last = $start . $letter++; return $next; }; } __DATA__ 1-5,6a-e,7,10-13
Re: need help to split list
by Cody Pendant (Prior) on Oct 24, 2003 at 21:58 UTC
    This takes care of your hyphenated numbers:
    $s = '1-5,6a-e,7,10-13'; @s = split ( ',', $s ); for (@s) { if (/(\d+)-(\d+)/) { $tmp = ''; for ( $1 .. $2 ) { $tmp .= "$_ "; } $_ = $tmp; } } print "@s"; # 1 2 3 4 5 6a-e 7 10 11 12 13


    ($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss') =~y~b-v~a-z~s; print
Re: need help to split list
by tilly (Archbishop) on Oct 25, 2003 at 05:48 UTC
    TIMTOWTDI. But some ways are nastier than others. ;-)
    $s='1-5,6a-e,7,10-13'; $s =~ s/,/ /g; $s =~ s/(\d+)-(\d+)/"{".(join ",", $1..$2)."}"/ge; $s =~ s/([a-z]+)-([a-z]+)/"{".(join ",", $1..$2)."}"/ge; $s =~ s/([A-Z]+)-([A-Z]+)/"{".(join ",", $1..$2)."}"/ge; print "$_\n" for glob($s);
Re: need help to split list
by daddyefsacks (Pilgrim) on Oct 25, 2003 at 00:13 UTC
    Well when I started there were no answers but oh well, I'll post my unworthy code anyway.
    set_ranged_data("1-5,6a-e,7w-z,10-13"); while(my @enumerated = get_next_range()) { print join("\n", @enumerated); print "\n"; } { my $data; my $pos; sub set_ranged_data { $data = shift; $pos = 0; 1; } sub get_next_range { return unless $data; my $end_pos = index($data, ","); $end_pos = length($data) if ($end_pos) < 0; my $range = substr($data, 0, $end_pos); substr($data, 0, $end_pos + 1) = ""; my @enumerated = _enumerate($range); wantarray ? return @enumerated : return \@enumerated } sub _enumerate { my $range = shift; return ($range) unless index($range, "-") > 0; my ($set, $end_point) = split /-/, $range; my ($begin_lead, $begin_point) = _decompose($set); return map {$begin_lead . $_} ($begin_point .. $end_point); } sub _decompose { my $set = shift; my ($lead, $point) = $set =~ /^(\d+)?(\D)?$/; return ($lead, $point) if ($lead && $point); return ("", $point) if ($point); return ("", $lead) if ($lead); return; } }
      Thanks for the code even though there were already answers. This is what the site is all about and the many different ways of doing this helps me to expand my knowledge of the language. Thanks, Ed
Re: need help to split list
by Roger (Parson) on Oct 25, 2003 at 12:04 UTC
    Another method, one liner using two splits:

    use strict; use Data::Dumper; my $list = "1-5,6a-e,7,9-12,89xDC-G"; my @s = map { my @t = split /-/; my $l = length($t[0]) - 1; # length less 1 $#t == 0 ? $_ : $t[0] + 0 eq $t[0] ? # pure number? $t[0] .. $t[1] : map { substr($t[0],0,$l) . $_ } substr($t[0],$l,1) .. $t[1] } split /,/, $list; print Dumper(\@s);
    And the output is:
    $VAR1 = [ 1, 2, 3, 4, 5, '6a', '6b', '6c', '6d', '6e', '7', 9, 10, 11, 12, '89xDC', '89xDD', '89xDE', '89xDF', '89xDG' ];
    Update: Typo fixed - 89xDC-G (was 89xFC-G). Thanks OverlordQ!

      Edit: edit of course it's typo *grin*
      my $list = "1-5,6a-e,7,9-12,89xFC-G";
      and then
                '89xDC',
                '89xDD',
                '89xDE',
                '89xDF',
                '89xDG'
      
      Did I miss something, or was it a typo? (89xFC changed to 89xDC)
        Thanks for catching my typo! Yes I ran the code with 89xDC-G, and when I posted the code, I pasted first, and then decided to try some new data, and instead of cut and paste the modified code again, just editted by hand...

Re: need help to split list
by qq (Hermit) on Oct 26, 2003 at 22:19 UTC
    Similar to gjb 's, I see:
    use warnings; use strict; my $str = '1-5,6a-e,7,10-13'; my @ranged; foreach ( split ',', $str ) { if ( /^(\d+)$/ ) { push @ranged, $1; } elsif ( /^(\d+)([a-z]+)-([a-z]+)$/ ) { push @ranged, map { "$1$_" } $2..$3; } elsif ( /^(\d+)-(\d+)$/ ) { push @ranged, $1..$2; } else { die "Don't know how to interpret '$_'"; } } print join "\n", @ranged;