3

I tried parsing a common string depiction of ranges (e.g. 1-9) into actual ranges (e.g. 1 .. 9), but often got weird results when including two digit numbers. For example, 1-10 results in the single value 1 instead of a list of ten values and 11-20 gave me four values (11 10 21 20), half of which aren't even in the expected numerical range:

put get_range_for('1-9');
put get_range_for('1-10');
put get_range_for('11-20');

sub get_range_for ( $string ) {

    my ($start, $stop) = $string.split('-');

    my @values = ($start .. $stop).flat;

    return @values;
}

This prints:

1 2 3 4 5 6 7 8 9
1
11 10 21 20

Instead of the expected:

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20

(I figured this out before posting this question, so I have answered below. Feel free to add your own answer if you'd like to elaborate).

Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99

2 Answers2

9

The problem is indeed that .split returns Str rather than Int, which the original answer solves. However, I would rather implement my "get_range_for" like this:

sub get_range_for($string) {
    Range.new( |$string.split("-")>>.Int )
}

This would return a Range object rather than an Array. But for iteration (which is what you most likely would use this for), this wouldn't make any difference. Also, for larger ranges the other implementation of "get_range_for" could potentially eat a lot of memory because it vivifies the Range into an Array. This doesn't matter much for "3-10", but it would for "1-10000000".

Note that this implementation uses >>.Int to call the Int method on all values returned from the .split, and then slips them as separate parameters with | to Range.new. This will then also bomb should the .split return 1 value (if it couldn't split) or more than 2 values (if multiple hyphens occurred in the string).

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
7

The result of split is a Str, so you are accidentally creating a range of strings instead of a range of integers. Try converting $start and $stop to Int before creating the range:

put get_range_for('1-9');
put get_range_for('1-10');
put get_range_for('11-20');

sub get_range_for ( $string ) {

    my ($start, $stop) = $string.split('-');

    my @values = ($start.Int .. $stop.Int).flat; # Simply added .Int here

    return @values;
}

Giving you what you expect:

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99
  • 2
    This approach is fine, but for larger ranges could potentially eat a lot of memory because you're vivifying the Range. I would write this as: `sub get_range_for($string) { Range.new( |$string.split("-")>>.Int ) }`. This would return the Range object, which you can iterate over *or* store in an array later, should you wish that. Note that this uses `>>.Int` to call the `Int` method on all values returned from the `.split`, and then slips them as separate parameters with `|` to `Range.new`. – Elizabeth Mattijsen Jun 30 '17 at 10:00
  • @ElizabethMattijsen Good call. Sometimes memory can be an issue. Please create an answer and include your solution. – Christopher Bottoms Jun 30 '17 at 16:28