2013-09-30 1 views
2

Résumé
Compte tenu HASHTABLE où certaines valeurs sont des tableaux, comment puis-je obtenir un tableau de hachages pour toutes les combinaisons possibles?Toutes les combinaisons de hachage de tableaux

Test Case

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

Lorsque la valeur d'une clé particulière est pas un tableau, il doit simplement être inclus tel quel dans chaque hachage, comme si elle était enveloppée dans un tableau.

Motivation
J'ai besoin pour générer des données de test donné une série de valeurs différentes options. Bien que je puisse utiliser [1,2].product([3,4],[5]) pour obtenir le produit cartésien de toutes les valeurs possibles, je préférerais utiliser des hachages pour étiqueter à la fois mon entrée et ma sortie afin que le code soit plus explicite que de simplement utiliser des indices de tableau.

+1

Pouvez-vous expliquer comment ce n'est pas une copie de la question liée? Voulez-vous dire quand une valeur n'est pas un tableau? – sawa

+2

@sawa Non, pour la question liée, il n'y aurait que deux résultats: '[{a: 1, b: 3}, {a: 2, b: 4}]'. La question liée concerne les valeurs appariées, pas les produits. – Phrogz

Répondre

1

Première tentative:

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

deuxième tentative, inspirée par @ réponse de Cary:

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 

En plus d'être plus élégante, la deuxième réponse est aussi 4.5x plus rapide que le premier quand la création d'un grand résultat (hachage 262k avec 6 clés chacun):

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

Vous pouvez remplacer 'v.is_a? (Array)? v: [v] 'avec' Array (v) ' – tihom

+1

@tihom Non, vous ne pouvez pas. Étant donné 'v = {}', 'v.is_a? (Array)? v: [v] 'est' [{}] ', et' Array (v) 'est' [] '. – sawa

+0

@sawa ... merci ... n'a pas considéré cette possibilité – tihom

2

Je suggère un peu de pré-traitement chanter pour garder le résultat général:

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

J'edited faire quelques améliorations, principalement l'utilisation de 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 

... bien que je préfère @ '2ème tentative' de Phrogz, qui, avec prétraitement 5=>[5], serait:

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 pour l'élégante solution de premier repos. Notez que cela nécessite que chaque valeur soit un tableau. Vous pouvez 'déstructurer' votre tableau 'p' comme ceci:' a.each {| (a, b) | h [a] = b} 'pour une amélioration de la vitesse d'environ 10%. – Phrogz

+0

Merci, @Phrogz. J'ai vu votre commentaire après avoir fait quelques changements. Je vous serais reconnaissant de toutes autres suggestions que vous souhaitez offrir. –

Questions connexes