2012-02-28 19 views
29

Tengo una matriz de palabras y quiero obtener un hash, donde las teclas son palabras y los valores son palabras.Matriz a hash: las palabras cuentan

¿Hay alguna forma más bella entonces mi:

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

¿Estás haciendo el curso SaaS de Berkeley? – Gordon

+2

Sí, tengo una solución, pero estoy buscando las mejores versiones. – demas

+1

si 'result [word]' no existe arrojará una excepción porque no hay '+' para nil. –

Respuesta

51

escribió el enfoque imperativo común y probablemente es la implementación más rápida en Ruby. Con un poco de refactorización, puede escribir una sola línea:

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

Otro enfoque imperativo el uso de Enumerable#each_with_object:

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

Un enfoque funcional utilizando abstracciones existentes:

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

Tenga en cuenta que esto sigue siendo O (n) en el tiempo, pero atraviesa la colección tres veces y crea dos objetos intermedios a lo largo del camino.

Finalmente: el contador de frecuencia/histograma es una abstracción común que encontrará en algunas bibliotecas como Facets: Enumerable#frequency.

require 'facets' 
wf = words.frequency 
+0

Puede ser simplemente, 'str.split (" ") .reduce (Hash.new (0)) {| h, w | pone h [w] + = 1; h} '? –

+1

Algunas pruebas de velocidad de pellizco de sal, ruby ​​2.0.0p451 en un macbook ejecutando Mavericks: Declarativo: '100.times {words.inject (Hash.new 0) {| h, w | h [w] + = 1; h}} ': avg 1.17s. Imperativo: '100.times {hist = Hash.new 0; words.each {| w | hist [w] + = 1}} ': avg 1.09s. 'words' era una matriz de 10k palabras aleatorias, la generación de la matriz solo tomó 0.2s promedio. es decir, Imperative fue aproximadamente un 9% más rápido. –

+0

Gracias por la última nota sobre Facets. He vuelto a implementar esto varias veces, y las facetas me ahorran la molestia de volver a hacerlo o iniciar mi propia lib estándar. Para otros, debes ver Facets, es como una extensión de la biblioteca estándar de Ruby. –

7

Con 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} 

no sé acerca de la eficiencia.

+1

De acuerdo con el método doc of the facets publicado por tokland, 'inject' es un proceso más lento. – Baldrick

+1

Además, si usas 'inject' y necesitas devolver el objeto al final del bloque como se indica arriba ('; h'), deberías usar 'each_with_object' en su lugar. – 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} 

como @mfilej dijo

0

hice algo similar a las respuestas anteriores, pero ligeramente diferente. Espero que pueda ayudar a alguien ...

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

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

puts hash 
+0

Esto es aproximadamente 10 veces más lento que otras soluciones proporcionadas. – Sixty4Bit

Cuestiones relacionadas