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

I have a table of commands to run and want to provide a convenient command line user interface to allow the user to select which (zero-based) indices from the table (implemented as a Perl array) to run.

Assuming a command table of eleven commands, say (index 0..10) you might try:

somecommand 4 # run index 4 from the command table somecommand 4-6 # run indices 4,5,6 from the command table somecommand 5- # run indices 5,6,7,8,9,10 from the command table somecommand -2 # run indices 0,1,2 from the command table
This covers the common case of running a range of contiguous commands. Suggestions for a nice command line user interface welcome.

Given the user interface described above, I wrote a function to convert from the user interface "range string" to a list suitable for use in a Perl array slice.

use strict; use warnings; # This function takes a "range string" and returns # a list that can be used in a Perl array slice. # Some examples should clarify: # Range string Return list # "4" (4) # "4-6" (4,5,6) # "5-" (5,6,7,...,$maxind) # "-2" (0,1,2) sub range_to_list { my $maxind = shift; # max index of (zero-based) list my $range = shift; # a "range string" (see examples above) my @ilist = split /-/, $range, 2; my $nlist = @ilist; $nlist == 1 || $nlist == 2 or die "range_to_list $nlist out of rang +e"; if ( $nlist == 1 ) { $ilist[0] =~ /^\d+$/ or die "range_to_list, '$ilist[0]' not nume +ric"; $ilist[0] <= $maxind or die "range_to_list, '$ilist[0]' out of r +ange"; return @ilist; } if ( $ilist[0] eq '' ) # e.g. "-5" case { $ilist[1] =~ /^\d+$/ or die "range_to_list, '$ilist[1]' not nume +ric"; $ilist[1] <= $maxind or die "range_to_list, '$ilist[1]' out of r +ange"; return ( 0 .. $ilist[1] ); } if ( $ilist[1] eq '' ) # e.g. "3-" case { $ilist[0] =~ /^\d+$/ or die "range_to_list, '$ilist[0]' not nume +ric"; $ilist[0] <= $maxind or die "range_to_list, '$ilist[0]' out of r +ange"; return ( $ilist[0] .. $maxind ); } # e.g. "3-5" case $ilist[0] =~ /^\d+$/ or die "range_to_list, '$ilist[0]' not numeric +"; $ilist[1] =~ /^\d+$/ or die "range_to_list, '$ilist[1]' not numeric +"; return ( $ilist[0] .. $ilist[1] ); } my $maxind = 10; print "maxind=$maxind\n"; for my $rstr ( "x", "1-2-3", "0", "10", "11", "4", "4-6", "7-", "-2", "-10", "-11", "-0", "10-", "11-" ) { print "rstr:$rstr:"; my @ret; eval { @ret = range_to_list( $maxind, $rstr ) }; if ($@) { print "...exc:$@"; } else { print "...ret:@ret:\n"; } } my @apples = ( ":zero:", ":one:", ":two:", ":three:", ":four:" ); print "apples ", @apples[range_to_list( $#apples, "1-3" )], "\n";
To clarify the spec, note that running the test program rl.pl above produces:
maxind=10 rstr:x:...exc:range_to_list, 'x' not numeric at rl.pl line 21. rstr:1-2-3:...exc:range_to_list, '2-3' not numeric at rl.pl line 39. rstr:0:...ret:0: rstr:10:...ret:10: rstr:11:...exc:range_to_list, '11' out of range at rl.pl line 22. rstr:4:...ret:4: rstr:4-6:...ret:4 5 6: rstr:7-:...ret:7 8 9 10: rstr:-2:...ret:0 1 2: rstr:-10:...ret:0 1 2 3 4 5 6 7 8 9 10: rstr:-11:...exc:range_to_list, '11' out of range at rl.pl line 28. rstr:-0:...ret:0: rstr:10-:...ret:10: rstr:11-:...exc:range_to_list, '11' out of range at rl.pl line 34. apples :one::two::three:
Implementation improvements of this inelegant first attempt welcome.

Replies are listed 'Best First'.
Re: Specifying a range of indices via the command line
by AnomalousMonk (Archbishop) on Feb 15, 2015 at 07:20 UTC

    If you have Perl version 5.10+, the  (?|pattern) construct (see Extended Patterns in perlre) supports a neat approach:

    c:\@Work\Perl>perl -wMstrict -le "use 5.010; ;; use Test::More 'no_plan'; use Test::NoWarnings; ;; my $rx_rng = qr{ \A (?| (\d*) - (\d+) | (\d+) - (\d*)) \z }xms; ;; my $max = 10; ;; for my $ar_vector ( [ '0-0', 0..0 ], [ '4-6', 4..6 ], [ '-2', 0..2 ], [ '3-', 3..$max ] +, ) { my ($rs, @rng) = @$ar_vector; is_deeply [ range($rs, $max, $rx_rng) ], \@rng, qq{'$rs'}; } ;; sub range { my ($rs, $r_max, $rx) = @_; ;; my ($lo, $hi) = $rs =~ $rx or die qq{not a range: '$rs'}; $lo = 0 unless length $lo; $hi = $r_max unless length $hi; ;; die qq{range inverted: '$rs'} if $lo > $hi; die qq{max out of range: '$rs'} if $hi > $r_max; ;; return $lo .. $hi; } " ok 1 - '0-0' ok 2 - '4-6' ok 3 - '-2' ok 4 - '3-' ok 5 - no warnings 1..5
    I will leave you to test the exceptions.


    Give a man a fish:  <%-(-(-(-<

Re: Specifying a range of indices via the command line
by BrowserUk (Patriarch) on Feb 15, 2015 at 12:24 UTC

    I think this catches all the same things as your code though the error message maybe different or less detailed:

    sub range_to_list { my( $max, $range ) = @_; my( $lo, $sep, $hi ) = $range =~ m[^(\d+)?(?:(-)(\d*))?$] or die " +Invalid range:$range\n"; $lo ||= 0; $hi ||= $sep ? $max : $lo; $_ > $max and die "$_ out of range\n" for $lo, $hi; return $lo .. $hi; }

    Output from your tests:

    C:\test>junk61 maxind=10 rstr:x:...exc:Invalid range:x rstr:1-2-3:...exc:Invalid range:1-2-3 rstr:0:...ret:0: rstr:10:...ret:10: rstr:11:...exc:11 out of range rstr:4:...ret:4: rstr:4-6:...ret:4 5 6: rstr:7-:...ret:7 8 9 10: rstr:-2:...ret:0 1 2: rstr:-10:...ret:0 1 2 3 4 5 6 7 8 9 10: rstr:-11:...exc:11 out of range rstr:-0:...ret:0 1 2 3 4 5 6 7 8 9 10: rstr:10-:...ret:10: rstr:11-:...exc:11 out of range apples :one::two::three:

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
    In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked
Re: Specifying a range of indices via the command line
by hdb (Monsignor) on Feb 15, 2015 at 08:34 UTC

    My proposal is to first normalize the input string to the form "start-end" using substitutions. This is simple if the string conforms to the specification. (I also assume that a pure "-" means a list from zero to the maximum.) Once this normalization is achieved, it is very simple to build the list:

    use strict; use warnings; sub range_to_list { my ($max, $str) = @_; $str =~ s/^-/0-/; # string starts with - $str =~ s/-$/-$max/; # string ends with - $str =~ s/^(\d+)$/$1-$1/; # just a number return ($str =~ /^(\d+)-(\d+)$/)&&($1<=$max)&&($2<=$max) ? ($1..$2) +: (); } my @tests = qw( 4 4-6 5- -2 - a 11- 2-7w -11 ); for my $test (@tests) { my @list = range_to_list 10, $test; print "$test:\t@list\n"; }
Re: Specifying a range of indices via the command line
by kroach (Pilgrim) on Feb 15, 2015 at 11:58 UTC

    How about this?

    use strict; use warnings; use feature 'say'; @ARGV or die 'No range specified'; # Check if the range was specified correctly. # This will filter out negative numbers as well. $ARGV[0] =~ /\A(?:\d+)?-(?:\d+)?\Z/ or die 'Invalid range'; my @commands = qw(zero one two three four); # In case one of the values was not specified it will become an empty +string my ($start, $end) = split '-', $ARGV[0]; # Since an empty string evaluates to false we can assign default value +s like this $start = 0 unless $start; $end = $#commands unless $end; # Negative indices are filtered out by the first regex so we only need + to check if the index is not too big. die "$start is out of range" unless $start < @commands; die "$end is out of range" unless $end < @commands; # The range is sure to be valid now so we can simply use .. to generat +e a list foreach my $index ($start .. $end) { say $commands[$index]; }
Re: Specifying a range of indices via the command line
by BrowserUk (Patriarch) on Feb 15, 2015 at 14:02 UTC

    BTW: Your testcode can be simplified also:

    my $maxind = 10; print "maxind=$maxind\n"; for my $rstr ( qw[ x 1-2-3 0 10 11 4 4-6 7- -2 -10 -11 -0 10- 11- ] ) + { my @ret = eval{ range_to_list( $maxind, $rstr ) }; print "rstr:$rstr:", $@ ? "...exc:$@" : "...ret:@ret:"; } my @apples = qw[ :zero: :one: :two: :three: :four: ]; print "apples ", @apples[range_to_list( $#apples, "1-3" )], "\n";

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
    In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked