В Ruby, как мне сделать хэш из массива?
У меня есть простой массив:
arr = ["apples", "bananas", "coconuts", "watermelons"]
У меня также есть функция f
, которая будет выполнять операцию с одним строковым вводом и возвращать значение. Эта операция очень дорогая, поэтому я хотел бы запомнить результаты в хеше.
Я знаю, что могу сделать желаемый хеш с чем-то вроде этого:
h = {}
arr.each { |a| h[a] = f(a) }
То, что я хотел бы сделать, это не инициализировать h, так что я могу просто написать что-то вроде этого:
h = arr.(???) { |a| a => f(a) }
Можно ли это сделать?
Ответы
Ответ 1
Скажите, что у вас есть функция с наивысшим названием: "f"
def f(fruit)
fruit + "!"
end
arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]
предоставит вам:
{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}
Обновлено:
Как уже упоминалось в комментариях, Ruby 1.8.7 представляет более сильный синтаксис для этого:
h = Hash[arr.collect { |v| [v, f(v)] }]
Ответ 2
Были некоторые быстрые, грязные тесты на некоторые из этих ответов. (Эти результаты могут быть не совсем идентичными с вашей версией Ruby, странным кэшированием и т.д., Но общие результаты будут похожи.)
arr
- это набор объектов ActiveRecord.
Benchmark.measure {
100000.times {
Hash[arr.map{ |a| [a.id, a] }]
}
}
Бенчмарк @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
В заключение
Просто потому, что Ruby является выразительным и динамичным, не означает, что вы всегда должны искать самое подходящее решение. Основной цикл был самым быстрым в создании хэша.
Ответ 3
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
Ответ 4
Вот что я, вероятно, напишу:
h = Hash[arr.zip(arr.map(&method(:f)))]
Простой, ясный, очевидный, декларативный. Что еще вам нужно?
Ответ 5
Ruby 2.6.0 обеспечивает более короткий синтаксис, передавая блок методу to_h
:
arr.to_h { |a| [a, f(a)] }
Ответ 6
Я делаю это, как описано в этой замечательной статье 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)) }
Подробнее о методе inject
: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject
Ответ 7
Другой, немного более ясный ИМХО -
Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Использование длины как 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 >
Ответ 8
в дополнение к ответу Владо Цингеля (я пока не могу добавить комментарий, поэтому я добавил ответ).
Впрыск также можно использовать следующим образом: блок должен вернуть аккумулятор. Только назначение в блоке возвращает значение назначения, и сообщается об ошибке.
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }