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