You can use each_with_object
for cleaner syntax
[1,1,2,3,3,3,4,5,5].each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 }
Note that order of arguments is reverse i.e. |number, hash|
Question
I have an array like so
[1,1,2,3,3,3,4,5,5]
and I want to count the number of occurrences of each number, which I'm trying to do like so
[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |hash,number| hash[number] += 1 }
The problem is I get the following error when I try to run it
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
Am I able to set the initial value like this, or am I getting this wrong?
Solution
You can use each_with_object
for cleaner syntax
[1,1,2,3,3,3,4,5,5].each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 }
Note that order of arguments is reverse i.e. |number, hash|
OTHER TIPS
You can use reduce
, but if you want so, you have to return the hash again:
[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) do |hash,number|
hash[number] += 1
hash
end
Without that the value of hash[number]
would be returned. The value of a hash assignment is the value itself. The block builds on the value you have returned previously.
Which means, that after the first it would try something like this: 1[1] += 1
, which of course does not work, because Fixnums do not implement the method []=
.
I like to use reduce
with hash.update
. It returns the array and I do not need the ; hash
part:
[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |h, n| h.update(n => h[n].next) }
Your question has been answered, but here's another way to skin the cat:
[Edited to adopt @David's suggestion.]
a = [1,1,2,3,3,3,4,5,5]
Hash[a.group_by(&:to_i).map {|g| [g.first, g.last.size]}]
This also works:
a.group_by{|e| e}.inject({}) {|h, (k,v)| h[k] = v.size; h}
and can be improved by adopting @spickermann's use of update
(or it's synonym, merge!
), to get rid of that irritating ; h
at the end:
a.group_by{|e| e}.inject({}) {|h, (k,v)| h.merge!(k => v.size)}
Formerly, I had:
Hash[*a.group_by(&:to_i).to_a.map {|g| [g.first, g.last.size]}.flatten]
I don't like to_i
here. I wanted to use to_proc
instead of ...group_by {|x| x|}
(as used in one of the solutions above) and was looking for a method m
that returns the receiver or its value. to_i
was the best I could do (e.g., 2.to_i => 2
), but only for Integers. Can anyone suggest a method that returns the receiver for a wide range of objects whose use with to_proc
makes it's purpose obvious?
That's simple:
[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]]
if result required in a Hash object
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}