I'd like to take input such as:
[1,2,4,5,6,7,9,13]
and turn it into something like the following:
[[1,2],[4,7],[9,9],[13,13]]
Each sub-array represents a range of integers.
I'd like to take input such as:
[1,2,4,5,6,7,9,13]
and turn it into something like the following:
[[1,2],[4,7],[9,9],[13,13]]
Each sub-array represents a range of integers.
Functional approach using Enumerable#chunk:
ranges = [1, 2, 4, 5, 6, 7, 9, 13]
.enum_for(:chunk) # .chunk for Ruby >= 2.4
.with_index { |x, idx| x - idx }
.map { |_diff, group| [group.first, group.last] }
#=> [[1, 2], [4, 7], [9, 9], [13, 13]]
How it works: once indexed, consecutive elements in the array have the same x - idx, so we use that value to chunk (grouping of consecutive items) the input array. Finally we just need to take the first and last elements of each group to build the pairs.
.map{ |min,max| min == max ? min : min .. max } will result in: [1..2, 4..7, 9, 13].[pairs.first[0], pairs.last[0]] to pairs.first[0] .. pairs.last[0] to get ranges in all positions: [1..2, 4..7, 9..9, 13..13].enum_for will no longer be necessary.chunk_whileThis is almost straight from the enumerable#slice_before method documentation:
ar = [1,2,4,5,6,7,9,13]
prev = ar[0]
ar.slice_before{|e|prev,prev2 = e,prev; prev2.succ != e}.map{|a|a.first..a.last}
#=> [1..2, 4..7, 9..9, 13..13]
This should work with characters, dates, anything with a .succ method.
An even easier solution than @tokland's very nice one is using chunk_while:
xs.chunk_while { |a, b| a + 1 == b }.map do |seq|
[seq.first, seq.last]
end
Note: chunk_while was introduced in Ruby 2.3
Hmm, well, it's not tokland's masterpiece, but I think it may be a good straightforward solution...
[1,2,4,5,6,7,9,13].inject([]) do |m, v|
if m.last.to_a.last == v.pred
m[-1][-1] = v
else
m << [v, v]
end
m
end