2012-02-28 4 views
29

У меня есть массив слов, и я хочу получить хэш, где ключи - слова и значения - это количество слов.Array to Hash: count count

Есть ли более красивый способ, то мой:

result = Hash.new(0) 
words.each { |word| result[word] += 1 } 
return result 
+0

Вы проводите курс Berkeley SaaS? – Gordon

+2

Да, у меня есть решение, но вы ищете лучшие версии. – demas

+1

если 'result [word]' не существует, он выдает исключение, потому что для 'n' нет' + '. –

ответ

51

Вы написали общий императивный подход и, вероятно, это быстрее, внедрение в Ruby. С немного рефакторинга, вы можете написать один вкладыш:

wf = Hash.new(0).tap { |h| words.each { |word| h[word] += 1 } } 

Другой императивный подход с использованием Enumerable#each_with_object:

wf = words.each_with_object(Hash.new(0)) { |word, acc| acc[word] += 1 } 

Функциональный подход с использованием существующих абстракций:

wf = words.group_by { |w| w }.map { |w, ws| [w, ws.length] }.to_h 

Обратите внимание, что это по-прежнему O (n) во времени, но три раза пересекает коллекцию и создает два промежуточных объекта на этом пути.

Итог: частота-счетчик/гистограмма - это общая абстракция, которую вы найдете в некоторых библиотеках, таких как Facets: Enumerable#frequency.

require 'facets' 
wf = words.frequency 
+0

Может быть просто: 'str.split (" ") .reduce (Hash.new (0)) {| h, w | помещает h [w] + = 1; h} '? –

+1

Некоторое испытание скорости соска, рубин 2.0.0p451 на macbook работает mavericks: Declarative: '100.times {words.inject (Hash.new 0) {| h, w | h [w] + = 1; h}} ': avg 1.17s. Imperative: '100.times {hist = Hash.new 0; words.each {| w | hist [w] + = 1}} ': avg 1.09s. 'words' был массивом из 10k случайных слов, генерация только одного массива заняла 0,2 с avg. то есть императив был примерно на 9% быстрее. –

+0

Благодарим вас за последнюю заметку о Facets. Я уже несколько раз перепрограммировал это, и аспекты избавляют меня от необходимости повторного использования или запуска моей собственной стандартной библиотеки. Для других вы должны проверить Facets, это как расширение стандартной библиотеки Ruby. –

7

С inject:

str = 'I have array of words and I want to get a hash, where keys are words' 
result = str.split.inject(Hash.new(0)) { |h,v| h[v] += 1; h } 

=> {"I"=>2, "have"=>1, "array"=>1, "of"=>1, "words"=>2, "and"=>1, "want"=>1, "to"=>1, "get"=>1, "a"=>1, "hash,"=>1, "where"=>1, "keys"=>1, "are"=>1} 

Я не знаю об эффективности.

+1

Согласно документу метода грани, опубликованного tokland, «инъекция» медленнее. – Baldrick

+1

Кроме того, если вы используете 'inject', и вам нужно вернуть объект в конце блока, как указано выше ('; h'), вместо этого вы должны использовать 'each_with_object'. – mfilej

2
irb(main):001:0> %w(foo bar foo bar).each_with_object(Hash.new(0)) { |w, m| m[w] += 1 } 
=> {"foo"=>2, "bar"=>2} 

, как @mfilej сказал

0

я сделал что-то подобное выше ответы, но немного другой. Надеюсь, это может помочь кому-то.

arr = ['a','b','a'] 
hash = {} 

arr.uniq.each do |e| 
    hash[e] = arr.count(e) 
end 

puts hash 
+0

Это примерно в 10 раз медленнее, чем другие решения. – Sixty4Bit