How to count identical string elements in a Ruby array

Solution 1:

Ruby v2.7+ (latest)

As of ruby v2.7.0 (released December 2019), the core language now includes Enumerable#tally - a new method, designed specifically for this problem:

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.tally
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.4+ (currently supported, but older)

The following code was not possible in standard ruby when this question was first asked (February 2011), as it uses:

  • Object#itself, which was added to Ruby v2.2.0 (released December 2014).
  • Hash#transform_values, which was added to Ruby v2.4.0 (released December 2016).

These modern additions to Ruby enable the following implementation:

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.group_by(&:itself).transform_values(&:count)
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.2+ (deprecated)

If using an older ruby version, without access to the above mentioned Hash#transform_values method, you could instead use Array#to_h, which was added to Ruby v2.1.0 (released December 2013):

names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

For even older ruby versions (<= 2.1), there are several ways to solve this, but (in my opinion) there is no clear-cut "best" way. See the other answers to this post.

Solution 2:

names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}

gives you

{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Solution 3:

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = Hash.new(0)
names.each { |name| counts[name] += 1 }
# => {"Jason" => 2, "Teresa" => 1, ....

Solution 4:

Now using Ruby 2.2.0 you can leverage the itself method.

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = {}
names.group_by(&:itself).each { |k,v| counts[k] = v.length }
# counts > {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}