2012-02-28 13 views
29

Mam tablicę słów i chcę uzyskać skrót, gdzie klucze są słowami, a wartości są liczbą słów.Array to Hash: liczba słów

Czy istnieje piękniejszy sposób wtedy moim:

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

Czy prowadzisz kurs Berkeley SaaS? – Gordon

+2

Tak, mam rozwiązanie, ale szukam lepszych wersji. – demas

+1

jeśli 'result [word]' does not exist wyrzucę wyjątek, ponieważ nie ma '+' dla zero. –

Odpowiedz

51

Napisałeś wspólne podejście imperatywne i prawdopodobnie jest to szybsza realizacja w Ruby. Przy odrobinie refaktoringu, można napisać jedno-liner:

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

Kolejny imperatyw podejście używając Enumerable#each_with_object:

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

podejściu funkcjonalnym wykorzystaniem istniejących abstrakcje:

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

Należy pamiętać, że to jest wciąż O (n) w czasie, ale trzykrotnie przechodzi przez kolekcję i tworzy po drodze dwa pośrednie obiekty.

Wreszcie: licznik częstotliwości/histogram jest powszechną abstrakcją, którą można znaleźć w niektórych bibliotekach, takich jak: Facets: Enumerable#frequency.

require 'facets' 
wf = words.frequency 
+0

Może być po prostu "str.split (" ") .reduce (Hash.new (0)) {| h, w | stawia h [w] + = 1; h.}? –

+1

Niektóre testy prędkości szczypta soli, ruby ​​2.0.0p451 na MacBooka z indywidualnymi uruchomieniami: Deklaratywny: '100.times {words.inject (Hash.new 0) {| h, w | h [w] + = 1; h}} ': avg 1.17s. Imperatyw: '100.times {hist = Hash.new 0; words.each {| w | hist [w] + = 1}} ': średni 1.09s. 'words' był tablicą 10k losowych słów, samo wygenerowanie tablicy zajęło 0.2 s avg. tj. Imperatyw był o 9% szybszy. –

+0

Dziękuję za ostatnią notatkę na temat Faset. Ponownie zaimplementowałem to już kilka razy, a aspekty oszczędzają mi trudności z powtórzeniem tego lub rozpoczęciem własnej standardowej biblioteki. Dla innych powinieneś zajrzeć do Facetów, to jest jak rozszerzenie standardowej biblioteki Rubiego. –

7

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

nie wiem o wydajności.

+1

Zgodnie z dokumentacją metody faset wysyłanej przez tokland, 'inject' jest wolniejsza. – Baldrick

+1

Również, jeśli używasz 'inject' i powinieneś zwrócić obiekt na końcu bloku jak wyżej ('; h'), powinieneś użyć '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} 

jak @mfilej powiedział

0

Zrobiłem coś podobnego do powyższych odpowiedzi, ale nieco inny. Mam nadzieję, że to pomoże komuś ..

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

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

puts hash 
+0

To około 10 razy mniej niż inne dostępne rozwiązania. – Sixty4Bit