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?

Was it helpful?

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}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top