2014-01-09 3 views
3

У меня есть массив объектов, и я хотел бы сгруппировать их на основе различия между атрибутами двух смежных элементов. Массив уже отсортирован по этому атрибуту. Например:Группировка массива путем сравнения двух смежных элементов

Оригинальный массив:

array = [a, b, c, d, e]

и

a.attribute = 1 
b.attribute = 3 
c.attribute = 6 
d.attribute = 9 
e.attribute = 10 

Если я хочу, чтобы сгруппировать элементы таким образом, чтобы разница между атрибутами 2 соседних элементов меньше или равный 2, результат должен выглядеть так:

END РЕЗУЛЬТАТ

result_array = [[a, b], [c], [d, e]]

ЧТО Я ИМЕЮ

def group_elements_by_difference(array, difference) 
    result_array = [] 
    subgroup = [] 
    last_element_attribute = array.first.attribute 
    array.each do |element| 
     if element.attribute <= (last_element_attribute + difference) 
     subgroup << element 
     else 
     #add the subgroup to the result_array 
     result_array << subgroup 
     subgroup = [] 
     subgroup << element 
     end 
     #update last_element_attribute 
     last_element_attribute = element.attribute 
    end 
    result_array << subgroup 
end 

ВОПРОС

Есть встроенная функция в Ruby, 1.9.3, такие как group_by, которые могли бы заменить мой group_elements_by_difference?

+0

'slice_before.with_index'? –

+2

'slice_before' с состоянием должно делать. –

ответ

4

По предложению Яна Дворжака, это решение использует slice_before и хэш, чтобы сохранить состояние:

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 

который дает

$ ruby group_by_adjacent_difference.rb 
. 

Finished in 0.0048 seconds 
1 example, 0 failures 

В качестве альтернативы, локальные переменные также могут быть использованы, чтобы сохранить состояние, хотя я считаю, это немного сложнее читать:

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 
5

Следующие использует цифры непосредственно, но алгоритм должен быть таким же, как при использовании атрибутов. Предполагается, что все цифры больше 0. Если нет, замените его тем, что работает.

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]] 

С атрибутами, сделайте l.attribute и т.д., и заменить 0 с фиктивным элементом которого атрибут 0.

1
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]] 
Смежные вопросы