2013-09-30 1 views
2

Резюме
Учитывая Hash, где некоторые значения являются массивы, как я могу получить массив хэшей для всех возможных комбинаций?Все комбинации для хэш массивов

Пример тест

options = { a:[1,2], b:[3,4], c:5 } 
p options.self_product 
#=> [{:a=>1, :b=>3, :c=>5}, 
#=> {:a=>1, :b=>4, :c=>5}, 
#=> {:a=>2, :b=>3, :c=>5}, 
#=> {:a=>2, :b=>4, :c=>5}] 

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

Мотивация
мне нужно генерировать тестовые данные приведены различные значения для различных вариантов. Хотя я могу использовать [1,2].product([3,4],[5]), чтобы получить декартово произведение всех возможных значений, я бы предпочел использовать хеши, чтобы иметь возможность маркировать как мои входные данные, так и вывод, чтобы код был более понятным, чем просто использовать индексы массивов.

+1

Можете ли вы объяснить, что это не дубликат связанного вопроса? Вы имеете в виду, когда значение не является массивом? – sawa

+2

@sawa Нет, для связанного вопроса будет только два результата: '[{a: 1, b: 3}, {a: 2, b: 4}]'. Связанный вопрос касается парных значений, а не продуктов. – Phrogz

ответ

1

Первая попытка:

class Hash 
    #=> Given a hash of arrays get an array of hashes 
    #=> For example, `{ a:[1,2], b:[3,4], c:5 }.self_product` yields 
    #=> [ {a:1,b:3,c:5}, {a:1,b:4,c:5}, {a:2,b:3,c:5}, {a:2,b:4,c:5} ] 
    def self_product 
    # Convert array values into single key/value hashes 
    all = map{|k,v| [k].product(v.is_a?(Array) ? v : [v]).map{|k,v| {k=>v} }} 
    #=> [[{:a=>1}, {:a=>2}], [{:b=>3}, {:b=>4}], [{:c=>5}]] 

    # Create the product of all mini hashes, and merge them into a single hash 
    all.first.product(*all[1..-1]).map{ |a| a.inject(&:merge) } 
    end 
end 

p({ a:[1,2], b:[3,4], c:5 }.self_product) 
#=> [{:a=>1, :b=>3, :c=>5}, 
#=> {:a=>1, :b=>4, :c=>5}, 
#=> {:a=>2, :b=>3, :c=>5}, 
#=> {:a=>2, :b=>4, :c=>5}] 

Вторая попытка, вдохновленный @ ответ Кэри:

class Hash 
    def self_product 
    first, *rest = map{ |k,v| [k].product(v.is_a?(Array) ? v : [v]) } 
    first.product(*rest).map{ |x| Hash[x] } 
    end 
end 

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

require 'benchmark' 
Benchmark.bm do |x| 
    n = *1..8 
    h = { a:n, b:n, c:n, d:n, e:n, f:n } 
    %w[phrogz1 phrogz2].each{ |n| x.report(n){ h.send(n) } } 
end 
#=>    user  system  total  real 
#=> phrogz1 4.450000 0.050000 4.500000 ( 4.502511) 
#=> phrogz2 0.940000 0.050000 0.990000 ( 0.980424) 
+0

Вы можете заменить 'v.is_a? (Array)? v: [v] 'с' Array (v) ' – tihom

+1

@tihom Нет, вы не можете. Учитывая 'v = {}', 'v.is_a? (Array)? v: [v] 'is' [{}] 'и' Array (v) 'is' [] '. – sawa

+0

@sawa ... спасибо ... не считал эту возможность – tihom

2

Я предлагаю немного предварительных процедур петь, чтобы сохранить результат вообще:

options = { a:[1,2], b:[3,4], c:5 } 
options.each_key {|k| options[k] = [options[k]] unless options[k].is_a? Array} 
=> {:a=>[1, 2], :b=>[3, 4], :c=>[5]} 

Я отредактирован, чтобы сделать несколько уточнений, главным образом использование inject({}):

class Hash 
    def self_product 
    f, *r = map {|k,v| [k].product(v).map {|e| Hash[*e]}} 
    f.product(*r).map {|a| a.inject({}) {|h,e| e.each {|k,v| h[k]=v}; h}} 
    end 
end 

... хотя я предпочитаю @ Phrogz в «2-ой попытки», который, с предварительной обработкой 5=>[5], будет:

class Hash 
    def self_product 
    f, *r = map {|k,v| [k].product(v)} 
    f.product(*r).map {|a| Hash[*a.flatten]} 
    end 
end 
+0

+1 для элегантного решения для первого и второго поколений. Обратите внимание, что для этого требуется, чтобы каждое значение представляло собой массив. Вы можете «разрушить» свой массив 'p' следующим образом:' a.each {| (a, b) | h [a] = b} 'для улучшения скорости примерно на 10%. – Phrogz

+0

Спасибо, @Phrogz. Я видел ваш комментарий после внесения нескольких изменений. Я хотел бы приветствовать любые дальнейшие предложения, которые вы, возможно, пожелаете предложить. –