I have an array of objects and I would like to group them based on the difference between the attributes of 2 adjacent elements. The array is already sorted by that attribute. F
array = [1, 3, 6, 9, 10]
prev = array[0]
p array.slice_before{|el| prev,el = el,prev; prev-el > 2}.to_a
# => [[1, 3], [6], [9, 10]]
The following uses numerals directly, but the algorithm should be the same as when you do it with attributes. It assumes that all numerals are greater than 0. If not, then replace it with something that works.
array = [1, 3, 6, 9, 10]
[0, *array].each_cons(2).slice_before{|k, l| l - k > 2}.map{|a| a.map(&:last)}
# => [[1, 3], [6], [9, 10]]
With attributes, do l.attribute, etc., and replace 0 with a dummy element whose attribute is 0.
Following Jan Dvorak's suggestion, this solution uses slice_before and a hash to keep the state:
class GroupByAdjacentDifference < Struct.new(:data)
def group_by(difference)
initial = { prev: data.first }
data.slice_before(initial) do |item, state|
prev, state[:prev] = state[:prev], item
value_for(item) - value_for(prev) > difference
end.to_a
end
def value_for(elem)
elem.attribute
end
end
require 'rspec/autorun'
describe GroupByAdjacentDifference do
let(:a) { double("a", attribute: 1) }
let(:b) { double("b", attribute: 3) }
let(:c) { double("c", attribute: 6) }
let(:d) { double("d", attribute: 9) }
let(:e) { double("e", attribute: 10) }
let(:data) { [a, b, c, d, e] }
let(:service) { described_class.new(data) }
context "#group_by" do
it "groups data by calculating adjacent difference" do
expect(service.group_by(2)).to eq([[a, b], [c], [d, e]])
end
end
end
which gives
$ ruby group_by_adjacent_difference.rb
.
Finished in 0.0048 seconds
1 example, 0 failures
In alternative, local variables could also be used to keep state, although I find it a bit harder to read:
class GroupByAdjacentDifference < Struct.new(:data)
def group_by(difference)
tmp = data.first
data.slice_before do |item|
tmp, prev = item, tmp
value_for(item) - value_for(prev) > difference
end.to_a
end
def value_for(elem)
elem.attribute
end
end