web-dev-qa-db-fra.com

Dans Ruby, comment créer un hachage à partir d'un tableau?

J'ai un tableau simple:

arr = ["apples", "bananas", "coconuts", "watermelons"]

J'ai également une fonction f qui effectuera une opération sur une seule entrée de chaîne et renverra une valeur. Cette opération est très coûteuse, je voudrais donc mémoriser les résultats dans le hachage.

Je sais que je peux faire le hachage souhaité avec quelque chose comme ceci:

h = {}
arr.each { |a| h[a] = f(a) }

Ce que j'aimerais faire, c'est de ne pas avoir à initialiser h, pour pouvoir simplement écrire quelque chose comme ceci:

h = arr.(???) { |a| a => f(a) }

Cela peut-il être fait?

69
Wizzlewott

Disons que vous avez une fonction avec un nom funtastic: "f"

def f(fruit)
   fruit + "!"
end

arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]

te donnera:

{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}

Mis à jour:

Comme mentionné dans les commentaires, Ruby 1.8.7 introduit une syntaxe plus agréable pour cela:

h = Hash[arr.collect { |v| [v, f(v)] }]
118
microspino

A fait des repères rapides et sales sur certaines des réponses données. (Ces résultats peuvent ne pas être exactement identiques aux vôtres en fonction de Ruby, mise en cache étrange, etc. mais les résultats généraux seront similaires.)

arr est une collection d'objets ActiveRecord.

Benchmark.measure {
    100000.times {
        Hash[arr.map{ |a| [a.id, a] }]
    }
}

Benchmark @ real = 0.860651, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.0, @ utime = 0.8500000000000005, @ total = 0.8500000000000005

Benchmark.measure { 
    100000.times {
        h = Hash[arr.collect { |v| [v.id, v] }]
    }
}

Benchmark @ real = 0,74612, @ cstime = 0,0, @ cutime = 0,0, @ stime = 0,010000000000000009, @ utime = 0,740000000000002, @ total = 0,750000000000002

Benchmark.measure {
    100000.times {
        hash = {}
        arr.each { |a| hash[a.id] = a }
    }
}

Benchmark @ real = 0,627355, @ cstime = 0,0, @ cutime = 0,0, @ stime = 0,010000000000000009, @ utime = 0,6199999999999974, @ total = 0,6299999999999975

Benchmark.measure {
    100000.times {
        arr.each_with_object({}) { |v, h| h[v.id] = v }
    }
}

Benchmark @ real = 1.650568, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.1299999999999999998, @ utime = 1.51, @ total = 1.64

En conclusion

Tout simplement parce que Ruby est expressif et dynamique, cela ne signifie pas que vous devriez toujours opter pour la meilleure solution. La boucle de base de chaque boucle a été la plus rapide pour créer un hachage.

52
dmastylo
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
32
megas

Voici ce que j'écrirais probablement:

h = Hash[arr.Zip(arr.map(&method(:f)))]

Simple, clair, évident, déclaratif. Que pourrais-tu vouloir de plus?

11
Jörg W Mittag

Ruby 2.6.0 permet une syntaxe plus courte en en passant un bloc au to_h méthode :

arr.to_h { |a| [a, f(a)] }
8
Timitry

Je le fais comme décrit dans ce grand article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }

Plus d'informations sur la méthode inject: http://Ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject

5
Vlado Cingel

Un autre, à mon humble avis un peu plus clair -

Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]

Utiliser la longueur comme f() -

2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
 => ["apples", "bananas", "coconuts", "watermelons"] 
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
 => {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11} 
2.1.5 :028 >
1
Shreyas