Zane Shelley | abc51c2 | 2020-11-09 21:35:35 -0600 | [diff] [blame] | 1 | package BitRange; |
| 2 | |
| 3 | use warnings; |
| 4 | use strict; |
| 5 | |
| 6 | use Data::Dumper; |
| 7 | |
| 8 | #------------------------------------------------------------------------------- |
| 9 | |
| 10 | # Takes a string of integers separated by ',' (concat) and ':' (range). Will |
| 11 | # return a sorted list of the expanded strings. |
| 12 | sub expand($) |
| 13 | { |
| 14 | my ( $str ) = @_; |
| 15 | |
| 16 | my @list; |
| 17 | for my $e ( split(/,/, $str) ) |
| 18 | { |
| 19 | if ( $e =~ /([0-9]+):([0-9]+)/ ) |
| 20 | { |
| 21 | push @list, $_ foreach ( int($1)..int($2) ); |
| 22 | } |
| 23 | else |
| 24 | { |
| 25 | push @list, int($e); |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | return @list; |
| 30 | } |
| 31 | |
| 32 | #------------------------------------------------------------------------------- |
| 33 | |
| 34 | sub __combineConsecutiveRanges($$;$$); # because it is called recursively |
| 35 | |
| 36 | sub __combineConsecutiveRanges($$;$$) |
| 37 | { |
| 38 | my ( $in, $out, $first, $last ) = @_; |
| 39 | |
| 40 | # Check if there are any elements in the input list. |
| 41 | if ( 0 < scalar @{$in} ) |
| 42 | { |
| 43 | # Check if we have found any previous range elements. |
| 44 | if ( defined $first ) |
| 45 | { |
| 46 | if ( defined $last ) |
| 47 | { |
| 48 | # We have at least two in a range. Check if the next one is in |
| 49 | # the consecutive range. |
| 50 | if ( $last + 1 == $in->[0] ) |
| 51 | { |
| 52 | $last = shift @{$in}; |
| 53 | } |
| 54 | # This range is done. Add to the list and start the next range. |
| 55 | else |
| 56 | { |
| 57 | push @{$out}, "$first:$last"; |
| 58 | $first = shift @{$in}; |
| 59 | $last = undef; |
| 60 | } |
| 61 | } |
| 62 | else |
| 63 | { |
| 64 | # Only the first element in the range so far. Check if the next |
| 65 | # one is in the consecutive range. |
| 66 | if ( $first + 1 == $in->[0] ) |
| 67 | { |
| 68 | $last = shift @{$in}; |
| 69 | } |
| 70 | # This range is done. Add to the list and start the next range. |
| 71 | else |
| 72 | { |
| 73 | push @{$out}, "$first"; |
| 74 | $first = shift @{$in}; |
| 75 | $last = undef; |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | # No previous range elements. Get the first one. |
| 80 | else |
| 81 | { |
| 82 | $first = shift @{$in}; |
| 83 | $last = undef; # Just in case. |
| 84 | } |
| 85 | |
| 86 | # Iterate again. |
| 87 | __combineConsecutiveRanges($in, $out, $first, $last); |
| 88 | } |
| 89 | # Nothing else in the input list. Add any trailing range elements. |
| 90 | elsif ( defined $first ) |
| 91 | { |
| 92 | push @{$out}, "$first" . ((defined $last) ? ":$last" : ""); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | # Takes a reference to a list of integers. Any set of consecutive integers will |
| 97 | # be combined using the ':' character to represent a range |
| 98 | # (i.e. 0,1,2,3 => 0:3). The remaining non-consecutive integers will be combined |
| 99 | # with ',' character (i.e. 0,2,3,5 => 0,2:3,5). |
| 100 | sub compress($) |
| 101 | { |
| 102 | my ( $in ) = @_; |
| 103 | |
| 104 | # Next, combine all of the consecutive ranges. |
| 105 | my $out = []; |
| 106 | __combineConsecutiveRanges( $in, $out ); |
| 107 | |
| 108 | # Now, combine the non-consecutive elements and return the string. |
| 109 | return join( ',', @{$out} ); |
| 110 | } |
| 111 | |
| 112 | #------------------------------------------------------------------------------- |
| 113 | |
| 114 | 1; |