2015-05-27 3 views
0

Существует два массива хэша, и я хочу удалить «общие» элементы из двух массивов на основе определенных клавиш. Например:ruby ​​сравнить два массива хэша, с определенными ключами

array1 = [{a: '1', b:'2', c:'3'}, {a: '4', b: '5', c:'6'}] 
array2 = [{a: '1', b:'2', c:'10'}, {a: '3', b: '5', c:'6'}] 

и ключи критерии являются и б. Поэтому, когда я получаю результат что-то вроде

array1-array2 (don't have to overwrite '-' if there's better approach) 

будет ожидать, чтобы получить [{а: «4», Ь: «5», с: «6»}] синус мы использовали и b в качестве критерия сравнения. Он уничтожит второй элемент, поскольку значение для a отличается от array1.last и array2.last.

+2

Ваш вопрос остается неясным. Как вы собираетесь получить этот результат? Что с этим связаны ключи? – engineersmnky

+0

Вы можете освободить трюм, если вы его уточните. Если первый абзац моего ответа является точным, не стесняйтесь его использовать, изменить или * verbayim *. Кроме того, я предлагаю вам объяснить, почему в вашем примере 'array1.last' хранился, но' array1.first' не был. –

ответ

8

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

код

require 'set' 

def reject_partial_dups(array1, array2, keys) 
    set2 = array2.each_with_object(Set.new) do |h,s| 
    s << h.values_at(*keys) if (keys-h.keys).empty? 
    end 
    array1.reject do |h| 
    (keys-h.keys).empty? && set2.include?(h.values_at(*keys)) 
    end 
end 

Линия:

(keys-h.keys).empty? && set2.include?(h.values_at(*keys)) 

может быть упрощено до:

set2.include?(h.values_at(*keys)) 

, если ни одно из значений ключей в элементах (хешей) из array1: nil. Я создал набор (а не массив) от array2, чтобы ускорить поиск h.values_at(*keys) в этой строке.

Пример

keys = [:a, :b] 
array1 = [{a: '1', b:'2', c:'3'}, {a: '4', b: '5', c:'6'}, {a: 1, c: 4}] 
array2 = [{a: '1', b:'2', c:'10'}, {a: '3', b: '5', c:'6'}] 
reject_partial_dups(array1, array2, keys) 
    #=> [{:a=>"4", :b=>"5", :c=>"6"}, {:a=>1, :c=>4}] 

Объяснение

Сначала нужно создать set2

e0 = array2.each_with_object(Set.new) 
    #=> #<Enumerator: [{:a=>"1", :b=>"2", :c=>"10"}, {:a=>"3", :b=>"5", :c=>"6"}] 
    #  #:each_with_object(#<Set: {}>)> 

Pass первый элемент e0 и выполнить расчет блока.

h,s = e0.next 
    #=> [{:a=>"1", :b=>"2", :c=>"10"}, #<Set: {}>] 
h #=> {:a=>"1", :b=>"2", :c=>"10"} 
s #=> #<Set: {}> 
(keys-h.keys).empty? 
    #=> ([:a,:b]-[:a,:b,:c]).empty? => [].empty? => true 

так вычислить:

s << h.values_at(*keys) 
    #=> s << {:a=>"1", :b=>"2", :c=>"10"}.values_at(*[:a,:b] } 
    #=> s << ["1","2"] => #<Set: {["1", "2"]}> 

Pass второй (последний) элемент e0 к блоку:

h,s = e0.next 
    #=> [{:a=>"3", :b=>"5", :c=>"6"}, #<Set: {["1", "2"]}>] 
(keys-h.keys).empty? 
    #=> true 

так вычислить:

s << h.values_at(*keys) 
    #=> #<Set: {["1", "2"], ["3", "5"]}> 

set2 
    #=> #<Set: {["1", "2"], ["3", "5"]}> 

Отклонить элементы array1

Теперь мы перебираем array1, отвергая элементы, для которых блок принимает значение true.

e1 = array1.reject 
    #=> #<Enumerator: [{:a=>"1", :b=>"2", :c=>"3"}, 
    #     {:a=>"4", :b=>"5", :c=>"6"}, {:a=>1, :c=>4}]:reject> 

Первый элемент e1 передается к блоку:

h = e1.next 
    #=> {:a=>"1", :b=>"2", :c=>"3"} 
a = (keys-h.keys).empty? 
    #=> ([:a,:b]-[:a,:b,:c]).empty? => true 
b = set2.include?(h.values_at(*keys)) 
    #=> set2.include?(["1","2"] => true 
a && b 
    #=> true 

поэтому первый элемент e1 отклоняется. Следующая:

h = e1.next 
    #=> {:a=>"4", :b=>"5", :c=>"6"} 
a = (keys-h.keys).empty? 
    #=> true 
b = set2.include?(h.values_at(*keys)) 
    #=> set2.include?(["4","5"] => false 
a && b 
    #=> false 

так что второй элемент e1 не отклоняется. И наконец:

h = e1.next 
    #=> {:a=>1, :c=>4} 
a = (keys-h.keys).empty? 
    #=> ([:a,:c]-[:a,:b]).empty? => [:c].empty? => false 

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

b = set2.include?(h.values_at(*keys)) 
+0

вы заслуживаете одного, потому что. Черт. Это много, чтобы написать lol –

+2

Спасибо, @AmirRaminfar. Я понимаю, что для> 90% читателей это грубый перебор. Они будут смотреть на код и знать, что происходит. Я часто включаю такие детали gory, потому что считаю, что новичкам полезно работать с деталями, особенно, чтобы помочь им понять, как работают счетчики. –

+0

@CarySwoveland Спасибо за подробный ответ! Это действительно понятно и полезно. –

1

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

Общий подход будет:

  1. За каждый раз, когда в array1
  2. Проверьте, чтобы увидеть то же самое значение в массив2 имеет какие-либо ключи и значения с одинаковым значением
  3. Если они затем удалите это

Вы, вероятно, в конечном итоге с чем-то вроде array1.each_with_index { |h, i| h.delete_if {|k,v| array2[i].has_key?(k) && array2[i][k] == v } }

+1

Обратите внимание, что я только что написал его, и он сработал. Но я не испытал этого. Вы должны попробовать это самостоятельно. Это довольно легко с 'irb' –

Смежные вопросы