В Ruby, как сделать хеш из массива? - PullRequest
68 голосов
/ 31 мая 2010

У меня есть простой массив:

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

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

Я знаю, что могу сделать желаемый хэш примерно так:

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

Мне бы не хотелось инициализировать h, чтобы я мог написать что-то вроде этого:

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

Можно ли это сделать?

Ответы [ 7 ]

117 голосов
/ 31 мая 2010

Скажем, у вас есть функция с неимоверным именем: "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)] }]
52 голосов
/ 15 января 2015

Сделал несколько быстрых, грязных тестов по некоторым из приведенных ответов. (Эти результаты могут не совпадать с вашими на основании версии 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] }]
    }
}

Тест @ 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 }
    }
}

Тест @ 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 выразителен и динамичен, не означает, что вы всегда должны искать самое красивое решение. Базовый цикл каждого цикла был самым быстрым в создании хэша.

30 голосов
/ 01 июля 2012
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
11 голосов
/ 31 мая 2010

Это то, что я, вероятно, написал бы:

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

Простой, понятный, очевидный, декларативный. Что еще можно хотеть?

6 голосов
/ 03 января 2019

Ruby 2.6.0 включает более короткий синтаксис путем передачи блока методу to_h :

arr.to_h { |a| [a, f(a)] }
5 голосов
/ 28 ноября 2013

Я делаю это, как описано в этой замечательной статье 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

1 голос
/ 10 декабря 2014

Еще один, чуть понятнее ИМХО -

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 >
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...