2015-08-15 2 views
0

У меня есть два массива, содержащие хэши:Селективный слияние массива хэшей в Ruby,

a = [ 
    {:umc=>"11VE", :title=>"FOOBARS"}, 
    {:umc=>"1973", :title=>"ZOOBARS"}, 
    {:umc=>"1140", :title=>"TINYBAR"}, 
] 

b = [ 
    {:umc=>"11VE", :code=>"23"}, 
    {:umc=>"10EE", :code=>"99"}, 
    {:umc=>"1140", :code=>"44"}, 
    {:umc=>"1973", :code=>"55"}, 
] 

и хотел бы выборочно объединить их в другой массив с хэшей следующим образом:

c = [ 
    {:umc=>"11VE", :title=>"FOOBARS", :code=>"23"}, 
    {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"}, 
    {:umc=>"1140", :title=>"TINYBAR"} :code=>"44"}, 
] 

Я использую код

combo=(a+b).group_by{|h| h[:umc]}.map{|k,v| v.reduce(:merge)} 

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

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

+2

Как насчет ': УМС => "10EE"'? – falsetru

+0

Извините, что 10EE не должен появляться в первом массиве. Излишне говорить, что фактические данные имеют более 10 000 наименований. Спасибо за уловку. – berlin

ответ

2
b.reduce(a) do |memo, e| 
    (ae = memo.detect { |ae| ae[:umc] == e[:umc] }) && ae.merge!(e) 
    memo 
end 

Cary предложил «более читаемым» способ выразить слияние, плюс то ли мы не хотим мутировать исходный a массив, мы должны dup его элементы:

b.reduce(a.map(&:dup)) do |memo, e| 
    ae = memo.detect { |ae| ae[:umc] == e[:umc] } 
    ae.merge!(e) if ae 
    memo 
end 
+0

Хорошее решение, но следует отметить, что он мутирует элементы 'a'. Имо, он лучше читал бы, если бы первая строка была разделена на две части, а последняя была «ae.merge! (E) если ae'. –

+0

Вы также можете изменить 'b.reduce (a)' to '(a + b) .reduce ({})' в случае, если два хэша в 'a' имеют одинаковое значение для': umc'. (Это ошибка, которую я сделал, которая была отмечена OP.) –

+0

@CarySwoveland Я добавил '.dup', чтобы избежать мутации исходного массива' a', спасибо. Что касается вашего второго комментария, я не понял. Результат не будет вырезать 'a' любым средним значением, он просто сольет его с' b'. – mudasobwa

0

Не очень элегантный, но он должен быть достаточно эффективным вычислением, так как в среднем это O (n + m). Я сомневаюсь, что вы можете получить его быстрее, чем это. С другой стороны, он сжигает много места.

def selective_merge(a, b) 
    merged = {} 
    a.each {|v| merged[v[:umc]] = [v[:umc], v[:title]]} 
    b.each {|v| merged[v[:umc]] = merged[v[:umc]] << v[:code] if merged[v[:umc]]} 
    merged.map {|k,v| {:umc => v[0], :title => v[1], :code => v[2]}} 
end 
+0

Вы также можете сделать его более общим, перейдя в операцию выбора и сделайте его работу с большим количеством ключей в хешах. – ayckoster

+0

Это отличный ответ, но я все еще новичок в Ruby и SO. У двух исходных массивов может быть произвольное количество элементов, поэтому я хотел бы настроить код для слияния всех элементов обоих хэшей, если элемент: umc совпадает, не указывая их. – berlin

0

Вы можете изменить свой код следующим образом:

bb = b.select { |f| a.any? { |h| h[:umc] == f[:umc] } } 
    #=> [{:umc=>"11VE", :code=>"23"}, 
    # {:umc=>"1140", :code=>"44"}, 
    # {:umc=>"1973", :code=>"55"}] 
(a + bb).group_by { |g| g[:umc] }.map { |_,v| v.reduce(:merge) } 
    #=> [{:umc=>"11VE", :title=>"FOOBARS", :code=>"23"}, 
    # {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"}, 
    # {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}] 

, но это было бы более эффективным для расчета bb таким образом:

require 'set' 
umc_a_vals = a.map { |g| g[:umc] }.to_set 
    #=> #<Set: {"11VE", "1973", "1140"}> 
bb = b.select { |f| umc_a_vals.include(f[:umc]) } 
    #=> [{:umc=>"11VE", :code=>"23"}, 
    # {:umc=>"1140", :code=>"44"}, 
    # {:umc=>"1973", :code=>"55"}] 

Вот еще один способ:

f = a.group_by { |g| g }.map { |_,v| v.reduce(:merge) } 
    #=> [{:umc=>"11VE", :title=>"FOOBARS"}, 
    # {:umc=>"1973", :title=>"ZOOBARS"}, 
    # {:umc=>"1140", :title=>"TINYBAR"}] 
b.each_with_object(f) do |g,h| 
    (h.update(g[:umc]=>g) { |_,o,n| o.merge(n) }) if h.key?(g[:umc]) 
end.values 
    #=> [{:umc=>"11VE", :title=>"FOOBARS", :code=>"23"}, 
    # {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"}, 
    # {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}] 

Расчет f настолько похож на ваш код, что я не считаю необходимым объяснение.

теперь объединить хэш:

{ g[:umc]=g } 
    #=> { "11VE"=>{:umc=>"11VE", :code=>"23"} } 

в h если h имеет ключевое "11VE", что она делает. Для этого мы используем форму Hash#update (А.К.А. merge!), который использует блок:

{ |_,o,n| o.merge(n) } 

для определения значений ключей, которые присутствуют в обоих хешей быть объединены.

Переменные блока равны:

_ #=> "11VE" 
o #=> {:umc=>"11VE", :title=>"FOOBARS"} 
n #=> {:umc=>"11VE", :code=>"23"} 

поэтому результат расчета блока является:

o.merge(n) 
    #=> {:umc=>"11VE", :title=>"FOOBAR, :code=>"23"} 

, который является обновленной значение h[:umc].

Кроме того: я использовал локальную переменную _ для значения ключа, чтобы обратить внимание на то, что он не используется при расчете блока. Переменные o и n обычно используются для представления «старых» и «новых» значений, соответственно.

Объединение в h хешей, построенных из оставшихся значений b, производится аналогичным образом.

Последним шагом является извлечение значений h.

В качестве второго примера, предположим, что:

a = [ 
    {:umc=>"11VE", :title=>"FOOBARS"}, 
    {:umc=>"11VE", :title=>"ZOOBARS", :author=>"Billy-Bob"}, 
    {:umc=>"1140", :title=>"TINYBAR"} 
] 

Получаем:

f = a.each_with_object({}) { |g,h| h.update(g[:umc]=>g) { |_,o,n| o.merge(n) } } 
    #=> {"11VE"=>{:umc=>"11VE", :title=>"ZOOBARS", :author=>"Billy-Bob"}, 
    # "1140"=>{:umc=>"1140", :title=>"TINYBAR"}} 
b.each_with_object(f) do |g,h| 
    (h.update(g[:umc]=>g) { |_,o,n| o.merge(n) }) if h.key?(g[:umc]) 
end.values 
    #=> [{:umc=>"11VE", :title=>"ZOOBARS", :author=>"Billy-Bob", :code=>"23"}, 
    # {:umc=>"1140", :title=>"TINYBAR", :code=>"44"}] 
+0

Ваш второй пример работает отлично, но он, кажется, отбрасывает любой элемент в a, который не имеет соответствующего соответствия в b. Мне нужно сохранить ВСЕ элементы, независимо от соответствия с b. Я просто не хочу, чтобы b не появлялся в a. Я медленно работаю над каждым из предлагаемых решений, в основном, чтобы ускорить использование рубина (версия 2.0). – berlin

+0

Хороший улов. (Читатели: проблема со вторым предложенным мной методом заключалась в том, что эти 'g' и' h' являются элементами 'a' и' g [: umc] = h [: umc] ', первый хэш будет перезаписан второй). Я исправил его и привел другой пример. –

0

Попробуйте

# @a - Pivot array(elements that will be in result array) 
# @b - Array which contains intersecting elements 
# @group - attribute name, which identify similarity of elements 
def c_join(a, b, group) 
    result = [] 
    a.each do |a_i| 
    similar = b.detect{ |b_i| b_i[group] == a_i[group] } 
    result << a_i.merge(similar) 
    end 
    result 
end 

Тестирование

a = [ 
    {:umc=>"11VE", :title=>"FOOBARS"}, 
    {:umc=>"1973", :title=>"ZOOBARS"}, 
    {:umc=>"1140", :title=>"TINYBAR"}, 
] 

b = [ 
    {:umc=>"11VE", :code=>"23"}, 
    {:umc=>"10EE", :code=>"99"}, 
    {:umc=>"1140", :code=>"44"}, 
    {:umc=>"1973", :code=>"55"}, 
] 

c_join(a, b, :umc) 

результат

[ 
    {:umc=>"11VE", :title=>"FOOBARS", :code=>"23"}, 
    {:umc=>"1973", :title=>"ZOOBARS", :code=>"55"}, 
    {:umc=>"1140", :title=>"TINYBAR", :code=>"44"} 
] 
Смежные вопросы