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

Hi Monks,

I'm trying to design and implement a Perl module which takes a range list in string form and turns it into an object which represents the corresponding list of numbers and allows the calling code to access the list.

This is a bit of background:

An email server may be asked to operate on a number of messages at a time (e.g. 'DELETE' or 'FETCH'). Each message is identified by a number, and the messages a particular operation applies to is specified by a range-list. (e.g. DELETE 1, 4, 6-10)

A range-list is specified as a string of comma-separated elements (with optional whitespace). Each element is either a single number or a range (e.g. 10-20). A range is inclusive (i.e. it includes both end-points, so the range 10-12 includes messages 10, 11 and 12). Numbers may be specified multiple times, and ranges may overlap. The list may be in any particular order.

Examples:
1, 3, 5-7 specifies 1, 3, 5, 6, 7
1-5, 2, 4 specifies 1, 2, 3, 4, 5
1-3, 2-5 specified 1, 2, 3, 4, 5

I'm putting together my idea but any help would be greatly appreciated. Thanks,
  • Comment on Perl Module - take in a String, output object

Replies are listed 'Best First'.
Re: Perl Module - take in a String, output object
by oko1 (Deacon) on Nov 19, 2008 at 22:57 UTC

    Extremely silly, but amusingly short :)

    #!/usr/bin/perl -wl use strict; my @a=0..1e3; # Set higher than any reasonable message number my @list = ("1, 3, 5-7", "1-5, 2, 4", "1-3, 2-5"); for (@list){ my %h; s/-/../g; $h{$_}++ for @a[(eval $_)]; print join " ", sort {$a<=>$b} keys %h; }

    Output:

    1 3 5 6 7 1 2 3 4 5 1 2 3 4 5

    This relies on Perl interpreting your format (once dashes are replaced by double dots) as array subscripts.


    --
    "Language shapes the way we think, and determines what we can think about."
    -- B. L. Whorf
Re: Perl Module - take in a String, output object
by Fletch (Bishop) on Nov 19, 2008 at 20:49 UTC

    Set::IntSpan?

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Perl Module - take in a String, output object
by Your Mother (Archbishop) on Nov 19, 2008 at 21:18 UTC

    Probably you'll get better advice but this was a fun and straightforward little problem so-

    package MyRanger; use strict; use Carp; sub new { my $caller = shift; my $self = bless {}, $caller; $self->{_range_string} = shift || croak "You must provide a range" +; for my $entry ( split /\s*,\s*/, $self->{_range_string} ) { if ( $entry =~ /\A\d+\z/ ) { push @{$self->{_raw_range}}, $entry; } elsif ( $entry =~ /\A(\d+)-(\d+)\z/ ) { carp "Range '$entry' is impossible, skipping" and next if $1 > $2; $entry =~ s/-/../; push @{$self->{_raw_range}}, eval $entry; } else { croak "Illegal range entry '$entry' given"; } } @{$self->{_range}} = sort { $a <=> $b } keys %{ { map {$_ => 1} @{ +$self->{_raw_range}} } }; @{$self->{_range}} || croak "No range was parseable in ", $self->{ +_range_string}; $self->{_pos} = 0; $self; } sub next { my $self = shift; $self->{_range}->[$self->{_pos}++]; } 1; my @range_strings = ( "1, 3, 5-7", "1-5, 2, 4, 10, 100", "1-3, 2-5", "12-10", ); for my $string ( @range_strings ) { print "MyRanger->new with '$string'\n"; my $ranger = MyRanger->new($string); while ( my $item = $ranger->next() ) { print "\tNext item is: $item\n"; } }

    Extra stuff like-

    sub size { my $self = shift; scalar @{$self->{_range}}; }

    ...d'oh, left up to you. :)

      Fun!
      sub strings_to_one_span { my %numbers; @numbers{ map { (/(-?\d+)-(-?\d+)/ or /((-?\d+))/) ? ($1 .. $2) : () + } split /\s*,\s*/ } = () for @_; sort { $a <=> $b } keys %numbers }
      []s, HTH, Massa (κς,πμ,πλ)
Re: Perl Module - take in a String, output object
by gone2015 (Deacon) on Nov 20, 2008 at 00:43 UTC

    You didn't say what you wanted this object to do...

    If you want it to store the range list, and then be used as a sort of iterator, then you might want to split the work in two:

    • during the initialisation of the object, validate the ranges, remove all duplicates and overlaps and store the resulting list of ranges in ascending order.

    • implement a "next" method for use:

      while (my $num = $range->next) { }

    • housekeeping to reset the iterator, test its state etc.

    Of course, it may be that you seldom have large enough ranges to care about the size of a pre-calculated list.