2013-10-05 14 views
5

Mam tablicę Podobnie jakKorzystanie Hash.new jako wartości początkowej przy zmniejszaniu tablicę

[1,1,2,3,3,3,4,5,5] 

i chcę zliczyć liczbę wystąpień każdego numeru, który staram się robić tak jak

[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |hash,number| hash[number] += 1 } 

problemem jest to, że pojawia się następujący błąd, gdy próbuję go uruchomić

NoMethodError: undefined method `[]=' for 1:Fixnum 
    from (irb):6:in `block in irb_binding' 
    from (irb):6:in `each' 
    from (irb):6:in `reduce' 
    from (irb):6 

jestem w stanie ustawić wartość początkową jak to, czy ja się mylę?

Odpowiedz

7

Można użyć each_with_object dla czystszego składni

[1,1,2,3,3,3,4,5,5].each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 } 

Zauważ, że kolejność argumentów jest odwrotna tj |number, hash|

+0

zawsze zapominam, że istnieje 'each_with_object'. Uważam to za czystsze niż zwracanie hasza w każdej iteracji. –

+0

czystsze było słowo, którego szukałem :) – tihom

+0

Przyjemne użytkowanie ewo! –

6

reduce można używać, ale jeśli chcesz, tak, trzeba ponownie powrócić hash:

[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) do |hash,number| 
    hash[number] += 1 
    hash 
end 

Bez że wartość hash[number] będą zwracane. Wartość przypisania hasha jest samą wartością. Blok opiera się na wartości, którą wcześniej zwróciłeś.

Co oznacza, że ​​po pierwszym będzie spróbować czegoś takiego: 1[1] += 1, co oczywiście nie działa, ponieważ Fixnums nie implementują metody []=.

4

Lubię używać reduce z hash.update. Zwraca tablicę i nie muszę się ; hash udział:

[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |h, n| h.update(n => h[n].next) } 
+1

Nice! Nie byłem zaznajomiony z 'update'. Schowałem to. (Czytelnicy: zauważ, że mógłby użyć '+ 1' zamiast' next', jeśli to nie jest oczywiste). –

2

Twoje pytanie zostało odebrane, ale tu jest inny sposób na skórę kota: [. Zmieniano przyjąć @ sugestią Dawida]

a = [1,1,2,3,3,3,4,5,5] 
Hash[a.group_by(&:to_i).map {|g| [g.first, g.last.size]}] 

działa to również:

a.group_by{|e| e}.inject({}) {|h, (k,v)| h[k] = v.size; h} 

i można poprawić poprzez przyjęcie @ Użycie Spickermann z dnia update (lub jego synonim, merge!), aby pozbyć się tego irytującego ; h na koniec:

a.group_by{|e| e}.inject({}) {|h, (k,v)| h.merge!(k => v.size)} 

Dawniej miałem:

Hash[*a.group_by(&:to_i).to_a.map {|g| [g.first, g.last.size]}.flatten] 

Nie lubię to_i tutaj . Chciałem użyć to_proc zamiast ...group_by {|x| x|} (jak w jednym z powyższych rozwiązań) i szukałem metody m, która zwraca odbiornik lub jego wartość. to_i było najlepsze, co mogłem zrobić (np. 2.to_i => 2), ale tylko dla liczb całkowitych. Czy ktoś może zaproponować metodę, która zwraca odbiornik dla szerokiej gamy obiektów, których użycie z to_proc czyni to oczywistym?

+0

Zbyt skomplikowane;) Możesz uprościć swój kod do 'Hash [a.group_by (&: to_i) .map {| g | [g.first, g.last.size]}] "przynajmniej. Hash dziedziczy 'map' z' Enumerable', więc nie trzeba jej konwertować na 'Array'. Możesz także uniknąć spłaszczenia wynikowej tablicy, a tym samym operatora splat. Nie wspominając już o tym, że możesz podzielić obiekt przekazywany przez 'map' w argumentach bloku. Zgadzam się z tym, że Ruby nie ma metody ".id", która powraca sama (zepsuta Haskell;)) –

+0

Wielkie dzięki, David, zarówno za sugestię, jak i wyjaśnienie. –

1

To proste:

[1,1,2,3,3,3,4,5,5].group_by {|e| e}.collect {|k,v| [k,v.count]} 
#=> [[1, 2], [2, 1], [3, 3], [4, 1], [5, 2]] 

jeśli wynik wymagany w obiekcie Hash

Hash[[1,1,2,3,3,3,4,5,5].group_by {|e| e}.collect {|k,v| [k,v.count]}] 
#=> {1=>2, 2=>1, 3=>3, 4=>1, 5=>2} 
Powiązane problemy