Ich habe ein einfaches Array:
arr = ["apples", "bananas", "coconuts", "watermelons"]
Ich habe auch eine Funktion f
, die eine Operation an einer einzelnen Zeichenfolgeneingabe ausführt und einen Wert zurückgibt. Diese Operation ist sehr teuer, daher möchte ich die Ergebnisse im Hash speichern.
Ich weiß, dass ich mit so etwas den gewünschten Hash erstellen kann:
h = {}
arr.each { |a| h[a] = f(a) }
Was ich tun möchte, ist, h nicht initialisieren zu müssen, damit ich einfach so etwas schreiben kann:
h = arr.(???) { |a| a => f(a) }
Kann das gemacht werden?
... { |v| [v, f(v)] }
, aber das hat den Trick gemacht!*
neben*arr.collect
?*
sammelt je nach Kontext eine Liste in einem Array oder wickelt ein Array in einer Liste ab. Hier wird das Array in eine Liste abgewickelt (die als Elemente für den neuen Hash verwendet werden soll).*
undflatten
für eine einfachere Version :h = Hash[ arr.collect { |v| [ v, f(v) ] } ]
. Ich bin mir nicht sicher, ob es einen Fall gibt, den ich nicht sehe.Hash[*key_pairs.flatten]
einfachHash[key_pairs]
. Viel schöner undrequire 'backports'
wenn Sie noch nicht von 1.8.6 aktualisiert haben.Habe einige schnelle, schmutzige Benchmarks für einige der gegebenen Antworten durchgeführt. (Diese Ergebnisse sind möglicherweise nicht genau identisch mit Ihren, basierend auf der Ruby-Version, dem seltsamen Caching usw., aber die allgemeinen Ergebnisse sind ähnlich.)
arr
ist eine Sammlung von ActiveRecord-Objekten.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.12999999999999998, @ utime = 1.51, @ total = 1.64
Abschließend
Nur weil Ruby ausdrucksstark und dynamisch ist, heißt das nicht, dass Sie sich immer für die schönste Lösung entscheiden sollten. Die Basis jeder Schleife war die schnellste beim Erstellen eines Hash.
quelle
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
quelle
Ruby 2.6.0 ermöglicht eine kürzere Syntax, indem ein Block an die
to_h
Methode übergeben wird :arr.to_h { |a| [a, f(a)] }
quelle
Das würde ich wahrscheinlich schreiben:
h = Hash[arr.zip(arr.map(&method(:f)))]
Einfach, klar, offensichtlich, deklarativ. Was willst du mehr?
quelle
zip
genauso wie der nächste Typ, aber da wir bereits anrufenmap
, warum lassen wir es nicht dabei?h = Hash[ arr.map { |v| [ v, f(v) ] } ]
Gibt es einen Vorteil für Ihre Version, den ich nicht sehe?Ich mache es wie in diesem großartigen Artikel beschrieben: 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)) }
Weitere Informationen zur
inject
Methode: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-injectquelle
merge
für jeden Schritt der Iteration. Zusammenführen ist O (n), ebenso wie die Iteration. Dies ist also,O(n^2)
während das Problem selbst offensichtlich linear ist. In absoluten Zahlen habe ich dies nur auf einem Array mit 100.000 Elementen versucht und es dauerte730 seconds
, während die anderen in diesem Thread erwähnten Methoden irgendwo von0.7
bis dauerten1.1 seconds
. Ja, das ist eine Verlangsamung um Faktor 700 !Ein anderer, meiner Meinung nach etwas klarer -
Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Länge als f () verwenden -
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 >
quelle
Zusätzlich zur Antwort von Vlado Cingel (Ich kann noch keinen Kommentar hinzufügen, daher habe ich eine Antwort hinzugefügt).
Inject kann auch folgendermaßen verwendet werden: Der Block muss den Akkumulator zurückgeben. Nur die Zuordnung im Block gibt den Wert der Zuordnung zurück, und ein Fehler wird gemeldet.
array = ["apples", "bananas", "coconuts", "watermelons"] hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }
quelle