# format_number_list()
#
# take an array of integers and convert them to a
# human readable string, compressing consecutive numbers with a range notation
# ie   (-1,0,1,-3,5,6,6,7,8) becomes '-1..1, -3, 5..6, 6..8'
#
# Algorithm:
#  turn the array into a space separated string.  Then apply a substituting
#  regex to the string until the regex fails to find a match.  The regex
#  looks for 2 numbers separated by a space.  If the 2 numbers it finds
#  are consecutive, then we need to represent them as a sequence.  If It
#  happens that the first number is already part of a sequence, then we
#  replace what we found with only the second number.  If the first number
#  is not part of a sequence, we replace what we found with the two numbers
#  separated by '..'.  Otherwise we replace what we found with the two
#  numbers separated by ', '.
#
#  eg:
#   format_number_list(-1,0,1,-3,5,6,6,7,8)
#
#        Start:  -1 0 1 -3 5 6 6 7 8
#  Iteration 1:  -1..0 1 -3 5 6 6 7 8      match (-1 0) replace with -1..0
#  Iteration 2:  -1..1 -3 5 6 6 7 8        match (.0 1) replace with .1
#  Iteration 3:  -1..1, -3 5 6 6 7 8       match (.1 -3) replace with .1, -3
#  Iteration 4:  -1..1, -3, 5 6 6 7 8      match (-3 5) replace with  -3, 5
#  Iteration 5:  -1..1, -3, 5..6 6 7 8     match (5 6) replace with  5..6
#  Iteration 6:  -1..1, -3, 5..6, 6 7 8    match (.6 6) replace with .6, 6
#  Iteration 7:  -1..1, -3, 5..6, 6..7 8   match (6 7) replace with  6..7
#  Iteration 8:  -1..1, -3, 5..6, 6..8     match (.7 8) replace with .8
#

sub format_number_list {
  {
    no warnings qw(numeric);
    die "The list contains a non-integer" if grep { $_ ne int $_ } @_;
  }
  my $str = join ' ', @_;
  while ($str =~ s/(-?)(-?\d+) (-?\d+)/($2+1 == $3) ? ($1) ? $1.$3 :
$1.$2.'-'.$3 : $1.$2.', '.$3/oe) {}
  return $str;
}

# expand_number_list()
#
# Take a string representing a list of comma separated numbers
# possibly having ranges denoted by two dots, and convert it to
# a list of integers.
#
# Algorithm:
# split the string on commas.  Loop through each element and split
# on '..'.  Check the validity of the results.  If we have two numbers
# then it represents a range, so expand it and save.  Otherwise we just
# have one number and we just save the value.
#

sub expand_number_list {
  my @list = ();
  foreach my $item (split /\s*,\s*/, shift) {
    my ($first, $second) = split /-/, $item;
    {
      no warnings qw(numeric);
      die "The string contains invalid data (non-integer value found in $item)"
        if ($first ne int $first || (defined $second && $second ne int $second));
      die "The string contains invalid data (openended range in $item)"
        if ($first eq '' || (defined $second && $second eq ''));
      die "The string contains invalid data (redundant range in $item)"
        if (defined $second && $second == $first);
      die "The string contains invalid data (reversed range in $item)"
        if (defined $second && $second <= $first);
    }
    push @list, (defined $second) ? $first+0 .. $second+0 : $first;
  }
  return @list;
}
