Welcome to the fourth installment of Enumerating Enumerable, a series of articles in which I challenge myself to do a better job of documenting Ruby’s Enumerable
module than RubyDoc.org does. In this article, I’ll cover Enumerable#count
, one of the new methods added to Enumerable in Ruby 1.9.
In case you missed the earlier installments, they’re listed (and linked) below:
Enumerable#count Quick Summary
In the simplest possible terms | How many items in the collection meet the given criteria? |
---|---|
Ruby version | 1.9 only |
Expects | Either:
|
Returns | The number of items in the collection that meet the given criteria. |
RubyDoc.org’s entry | Enumerable#count |
Enumerable#count and Arrays
When used on an array and an argument is provided, count
returns the number of times the value of the argument appears in the array:
# How many instances of "zoom" are there in the array? ["zoom", "schwartz", "profigliano", "zoom"].count("zoom") => 2 # Prior to Ruby 1.9, you'd have to use this equivalent code: ["zoom", "schwartz", "profigliano", "zoom"].select {|word| word == "zoom"}.size => 2
When used on an array and a block is provided, count
returns the number of items in the array for which the block returns true
:
# How many members of "The Mosquitoes" (a Beatles-esque band that appeared on # "Gilligan's Island") have names following the "B*ngo" format? ["Bingo", "Bango", "Bongo", "Irving"].count {|bandmate| bandmate =~ /B[a-z]ngo/} => 3 # Prior to Ruby 1.9, you'd have to use this equivalent code: ["Bingo", "Bango", "Bongo", "Irving"].select {|bandmate| bandmate =~ /B[a-z]ngo/}.size
RubyDoc.org says that when count
is used on an array without an argument or a block, it simply returns the number of items in the array (which is what the length
/size
methods do). However, when I’ve tried it in irb and ruby, I got results like this:
[1, 2, 3, 4].count => #<Enumerable::Enumerator:0x189d784>
Enumerable#count and Hashes
As with arrays, when used on a hash and an argument is provided, count
returns the number of times the value of the argument appears in the hash. The difference is that for the comparison, each key/value pair is treated as a two-element array, with the key being element 0 and the value being element 1.
# Here's a hash where the names of recent movies are keys # and their metacritic.com ratings are the corresponding values. movie_ratings = {"Get Smart" => 53, "Kung Fu Panda" => 88, "The Love Guru" => 15, "Sex and the City" => 51, "Iron Man" => 93} => {"Get Smart"=>53, "Kung Fu Panda"=>88, "The Love Guru"=>15, "Sex and the City"=>51, "Iron Man"=>93} # This statement will return a count of 0, since there is no item in movie_ratings # that's just plain "Iron Man". movie_ratings.count("Iron Man") => 0 # This statement will return a count of 1, since there is an item in movie_ratings # with the key "Iron Man" and the corresponding value 93. movie_ratings.count(["Iron Man", 93]) => 1 # This statement will return a count of 0. There's an item in movie_ratings # with the key "Iron Man", but its corresponding value is NOT 92. movie_ratings.count(["Iron Man", 92]) => 0
count
is not useful when used with a hash and an argument. It will only ever return two values:
1
if the argument is a two-element array and there is an item in the hash whose key matches element [0] of the array and whose value matches element [1] of the array.0
for all other cases.
When used with a hash and a block, count
is more useful. count
passes each key/value pair in the hash to the block, which you can “catch” as either:
- A two-element array, with the key as element 0 and its corresponding value as element 1, or
- Two separate items, with the key as the first item and its corresponding value as the second item.
Each key/value pair is passed to the block and count
returns the number of items in the hash for which the block returns true
.
# Once again, a hash where the names of recent movies are keys # and their metacritic.com ratings are the corresponding values. movie_ratings = {"Get Smart" => 53, "Kung Fu Panda" => 88, "The Love Guru" => 15, "Sex and the City" => 51, "Iron Man" => 93} => {"Get Smart"=>53, "Kung Fu Panda"=>88, "The Love Guru"=>15, "Sex and the City"=>51, "Iron Man"=>93} # How many movie titles in the collection start # in the first half of the alphabet? # (Using a one-parameter block) movie_ratings.count {|movie| movie[0] <= "M"} => 3 # How many movie titles in the collection start # in the first half of the alphabet? # (This time using a two-parameter block) movie_ratings.count {|title, rating| title <= "M"} => 3 # Here's how you'd do it in pre-1.9 Ruby: movie_ratings.select {|movie| movie[0] <= "M"}.size => 3 # or... movie_ratings.select {|title, rating| title <= "M"}.size => 3 # How many movies in the collection had a rating # higher than 80? # (Using a one-parameter block) movie_ratings.count {|movie| movie[1] > 80} => 2 # How many movies in the collection had a rating # higher than 80? # (This time using a two-parameter block) movie_ratings.count {|title, rating| rating > 80} => 2 # Here's how you'd do it in pre-1.9 Ruby: movie_ratings.select {|title, rating| rating > 80}.size => 2 # How many movies in the collection have both: # - A title starting in the second half of the alphabet? # - A rating less than 50? # (Using a one-parameter block) movie_ratings.count {|movie| movie[0] >= "M" && movie[1] < 50} => 1 # How many movies in the collection have both: # - A title starting in the second half of the alphabet? # - A rating less than 50? # (This time using a two-parameter block) movie_ratings.count {|title, rating| title >= "M" && rating < 50} => 1 # Here's how you'd do it in pre-1.9 Ruby: movie_ratings.select {|title, rating| title >= "M" && rating < 50}.size => 1
(You should probably skip The Love Guru completely, or at least until it gets aired on TV for free.)