Categories
Uncategorized

Enumerating Enumerable: Enumerable#count

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:

  1. all?
  2. any?
  3. collect / map

Enumerable#count Quick Summary

Graphic representation of the Enumberable#count method in Ruby

In the simplest possible terms How many items in the collection meet the given criteria?
Ruby version 1.9 only
Expects Either:

  • An argument to be matched against the items in the collection
  • A block containing an expression to test the items in the collection
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:

  1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
  2. 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.)

Categories
Uncategorized

Enumerating Enumerable: Enumerable#collect/Enumerable#map

Here we are the third installment of Enumerating Enumerable, my attempt to do a better job of documenting Ruby’s Enumerable module than RubyDoc.org does. In this installment, I cover Enumerable#collect and its syntactic sugar twin (and the one I prefer), Enumerable#map.

In case you missed the earlier installments, they’re listed (and linked) below:

  1. all?
  2. any?

Enumerable#collect/Enumerable#map Quick Summary

Graphic representation of Ruby\'s \"Enumerable#collect / Enumerable#map\" methods

In the simplest possible terms Create a new array by performing some operation on every item in the given collection. collect and map are synonyms — you can use either. I personally prefer map as it’s shorter and makes sense if you think of the method as being like functional mapping.
Ruby version 1.8 and 1.9
Expects A block containing the criteria. This block is optional, but you’re likely to use one in most cases.
Returns An array made up of items created by performing some operation on the given collection.
RubyDoc.org’s entry Enumerable#collect / Enumerable#map

Enumerable#collect/Enumerable#map and Arrays

When used on an array and a block is provided, collect/map passes each item to the block, where the operation in the block is performed on the item and the result is then added to the result array. Note the the result array has the same number of elements as the given array.

[1, 2, 3, 4].map {|number| number ** 2}
=> [1, 4, 9, 16]

["Aqua", "Bat", "Super", "Wonder Wo"].map {|adjective| adjective + "man"}
=> ["Aquaman", "Batman", "Superman", "Wonder Woman"]

When the block is omitted, collect/map uses this implied block: {|item| item}, which means when applied on an array without a block, collect/map is the identity function — the resulting array is the same as the given array.

[1, 2, 3, 4].map
=> [1, 2, 3, 4]

["Aqua", "Bat", "Super", "Wonder Wo"].map
=> ["Aqua", "Bat", "Super", "Wonder Wo"]

Enumerable#collect/Enumerable#map and Hashes

When used on a hash and a block is provided, collect and map pass each key/value pair in the hash to the block, which you can “catch” as either:

  1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
  2. 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, where the operation in the block is performed on the item and the result is then added to the result array. Note the the result array has the same number of elements as the given array.

burgers = {"Big Mac" => 300, "Whopper with cheese" => 450, "Wendy's Double with cheese" => 320}

# What if I had just half a burger?
burgers.map {|burger| burger[1] / 2}
=> [160, 150, 225]

burgers.map {|sandwich, calories| calories / 2}
=> [160, 150, 225]

burgers.map {|burger| "Have a tasty #{burger[0]}!"}
=> ["Have a tasty Wendy's Double with cheese!", "Have a tasty Big Mac!",
 "Have a tasty Whopper with cheese!"]

burgers.map {|sandwich, calories| "Have a tasty #{sandwich}!"}
=> ["Have a tasty Wendy's Double with cheese!", "Have a tasty Big Mac!",
 "Have a tasty Whopper with cheese!"]

burgers.map {|sandwich, calories| ["Half a #{sandwich}", calories / 2]}
=> [["Half a Wendy's Double with cheese", 160], ["Half a Big Mac", 150],
 ["Half a Whopper with cheese", 225]]

When the block is omitted, collect/map uses this implied block: {|item| item}, which means when applied on an hash without a block, collect/map returns an array containing a set of two-item arrays, one for each key/value pair in the hash. For each two-item array, item 0 is the key and item 1 is the corresponding value.

burgers = {"Big Mac" => 300, "Whopper with cheese" => 450, "Wendy's Double with cheese" => 320}

burgers.map
=> [["Wendy's Double with cheese", 320], ["Big Mac", 300], ["Whopper with cheese", 450]]

Special Case: Using Enumerable#collect/Enumerable#map on Empty Arrays and Hashes

When applied to an empty array or hash, with or without a block, collect and map always return an empty array.

# Let's try it with an empty array
[].map
=> []

[].map {|item| item * 2}
=> []

# Now let's try it with an empty hash
{}.map
=> []

{}.map {|sandwich, calories| "Have a tasty #{sandwich}!"}
=> []

Categories
Uncategorized

Enumerating Enumerable: Enumerable#any?

Welcome to the second installment of Enumerating Enumerable, my project to do a better job of documenting Ruby’s Enumerable module than RubyDoc.org. This installment will cover Enumerable#any?, which I like to think of as Enumerable.all?‘s more easy-going cousin (I covered Enumerable.all? in the previous installment).

Enumerable#any? Quick Summary

Graphic representing Ruby\'s Enumerable#any? method

In the simplest possible terms Do any of the items in the collection meet the given criteria?
Ruby version 1.8 and 1.9
Expects A block containing the criteria. This block is optional, but you’re likely to use one in most cases.
Returns true if any of the items in the collection meet the given criteria.

false if none of the items in the collection does not meet the given criteria.

RubyDoc.org’s entry Enumerable#any?

Enumerable#any? and Arrays

When used on an array and a block is provided, any? passes each item to the block. If the block returns true for any item during this process, any? returns true; otherwise, it returns false.

# "Fromage de Montagne de Savoie" is the longest-named cheese in this list
# at a whopping 29 characters
cheeses = ["feta", "cheddar", "stilton", "camembert", "mozzarella", "Fromage de Montagne de Savoie"]

cheeses.any? {|cheese| cheese.length >= 25}
=> true

cheeses.any? {|cheese| cheese.length >= 35}
=> false

When the block is omitted, any? uses this implied block: {|item| item}. Since everything in Ruby evaluates to true except for false and nil, using any? without a block is effectively a test to see if any of the items in the collection evaluate to true (or conversely, if all the values in the array evaluate to false or nil).

cheeses.any?
=> true

cheeses = [false, nil]
=> [false, nil]

cheeses.any?
=> false

# Remember that in Ruby, everything except for false and nil evaluates to true:
cheeses << 0
=> [false, nil, 0]

>> cheeses.any?
=> true

Enumerable#any? and Hashes

When used on a hash and a block is provided, any? passes each key/value pair in the hash to the block, which you can “catch” as either:

  1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
  2. Two separate items, with the key as the first item and its corresponding value as the second item.

If the block returns true for any item during this process, any? returns true; otherwise, it returns false.

# Here's a hash where for each key/value pair, the key is a programming language and
# the corresponding value is the year when that language was first released
# The keys range in value from "Javascript" to "Ruby", and the values range from
# 1987 to 1996
languages = {"Javascript" => 1996, "PHP" => 1994, "Perl" => 1987, "Python" => 1991, "Ruby" => 1993}

languages.any? {|language| language[0] < "Pascal"}
=> true

languages.any? {|language, year_created| language < "Pascal"}
=> true

languages.any? {|language| language[0] < "Fortran"}
=> false

languages.any? {|language, year_created| language < "Fortran"}
=> false

languages.any? {|language| language[0] >= "Basic" and language[1] <= 1995}
=> true

languages.any? {|language, year_created| language >= "Basic" and year_created <= 1995}
=> true

languages.any? {|language| language[0] >= "Basic" and language[1] <= 1985}
=> false

languages.any? {|language, year_created| language >= "Basic" and year_created <= 1985}
=> false

Using any? without a block on a hash is meaningless, as it will always return true. When the block is omitted, any? uses this implied block: {|item| item}. In the case of a hash, item will always be a two-element array, which means that it will never evaluate as false nor nil.

And yes, even this hash, when run through any?, will still return true:

{false => false, nil => nil}.any?
=> true

Special Case: Using Enumerable#any? on Empty Arrays and Hashes

When applied to an empty array or hash, with or without a block, any? always returns false. That’s because with an empty collection, there are no values to process and return a true value.

Let’s look at the case of empty arrays:

cheeses = []
=> []

cheeses.any? {|cheese| cheese.length >= 25}
=> false

cheeses.any?
=> false

# Let's try applying "any?" to a non-empty array
# using a block that ALWAYS returns true:
["Gruyere"].any? {|cheese| true}
=> true

# ...but watch what happens when we try the same thing
# with an EMPTY array!
[].any? {|cheese| true}
=> false

…now let’s look at the case of empty hashes:

languages = {}
=> {}

languages.any? {|language| language[0] < "Pascal"}
=> false

languages.any? {|language, year_created| language < "Pascal"}
=> false

languages.any?
=> false

# Let's try applying "any?" to a non-empty hash
# using a block that ALWAYS returns true:
{"Lisp" => 1959}.any? {|language| true}
=> true

# ...but watch what happens when we try the same thing
# with an EMPTY hash!
{}.any? {|language| true}
=> false

Categories
Uncategorized

How Map/Reduce/Filter Can Rock Your World

Better late than never: C# 3.0 will feature map, reduce and filter, and Dare “Carnage4Life” Obasanjo explains how to rock these features. He concludes the article with “If your programming language doesn’t support lambda functions or have map/reduce/filter functions built in, you just might be a Blub Programmer who is missing out on being more productive because your programming language doesn’t support ‘esoteric’ or ‘weird’ features,” which sounds kind of weird in an article about C#, a language that could only be Blubbier if its name were BlubLang or Blub#. I suspect I’ve woken up in some upside-down parallel universe again!

Categories
Uncategorized

Enumerating Ruby’s “Enumerable” Module, Part 3: “detect”, a.k.a. “find”

Enumerable#find: A magnifying glass focused on a ruby

Welcome to the third installment in my series of articles on the methods of Ruby’s Enumerable module. This series is meant to address some of the shortcomings in the official documentation

In case you missed the first two, here they are:

  1. all? and any?
  2. collect/map

In this installment, I’m going to cover the find method. This is a particularly interesting one because it covers detect/find‘s little-talked about optional parameter.

detect, a.k.a. find

  • In plain language: What’s the first item in the collection that meets the given criteria?
  • Ruby.Doc.org’s entry: Enumerable#detect / Enumerable#find
  • Expects:
    • A block containing the criteria.
    • An optional argument containing a proc that calculates a “default” value — that is, the value to return if no item in the collection matches the criteria.
  • Returns:
    • The first item in the collection that matches the criteria, if one exists.
    • If no such item exists in the collection:
      • nil if no argument is provided
      • The value of the argument if one is provided
  • detect and find are synonyms — you can use either. I personally prefer find, as it’s shorter and a good match with a related method, find_all. I also just think that “find” conveys the method’s functionality much better than “detect”.

    Using detect/find with Arrays

    When used on an array without an argument, detect/find passes each item from the collection to the block and…

    • If the current item causes the block to return a value that doesn’t evaluate to false, detect/find stops going through collection and returns the item.
    • If no item in the collection causes the block to return a value that doesn’t evaluate to false, detect/find returns nil.

    classic_rock_bands = ["AC/DC", "Black Sabbath", "Queen", "Scorpions"]
    
    classic_rock_bands.find {|band| band > "Led Zeppelin"}
    => "Queen"
    
    classic_rock_bands.find {|band| band > "ZZ Top"}
    => nil
    

    Using the optional argument is a topic big enough to merit its own section, which appears later in this article.

    Using detect/find with Hashes

    With hashes, detect/find passes each key/value pair in the hash to the block, which you can “catch” as either:

    1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
    2. Two separate items, with the key as the first item and its corresponding value as the second item.

    detect/find is one of those methods of Enumerable that works a little oddly since:

    • The result it returns depends on the order of the collection
    • We’re always told that hashes don’t really any order (there seems to be one, but it’s shrouded in mystery).

    metacritic_ratings = {"Juno" => 81, "American Gangster" => 76, \
                          "Golden Compass" => 51, "Meet the Spartans" => 9}
    => {"American Gangster"=>76, "Golden Compass"=>51, "Juno"=>81, "Meet the Spartans"=>9}
    
    metacritic_ratings.find {|metacritic_rating| metacritic_rating[1] > 80}
    => ["Juno", 81]
    
    metacritic_ratings.find {|film, rating| rating > 80}
    => ["Juno", 81]
    
    metacritic_ratings.find {|film, rating| rating > 90}
    => nil
    

    Using detect/find with the Optional Argument

    detect/find‘s optional argument lets you specify a proc or lambda whose return value will be the result in cases where no object in the collection matches the criteria.

    (Unfortunately, a complete discussion of procs and lambdas is beyond the scope of this article. I highly recommend looking at Eli Bendersky’s very informative article, Understanding Ruby blocks, Procs and methods.)

    I think that the optional argument is best explained through examples…

    classic_rock_bands = ["AC/DC", "Black Sabbath", "Queen", "Scorpions"]
    
    # Let's define a proc that will simply return the default band's name
    # for cases where none of the bands in the array meets the criteria.
    default_band = Proc.new {"ABBA"}
    
    # Procs are objects, so using a proc's name alone isn't sufficient
    to invoke its code -- doing so will simply return the proc object.
    default_band
    => #<Proc:0x00553f34@(irb):31>
    # (The actual value will be different for you, but you get the idea.)
    
    # To call a proc, you have to use its "call" method:
    default_band.call
    => "ABBA"
    
    # detect/find calls the "call" method of the object you provide as the argument
    # if no item in the collection matches the criteria in the block.
    classic_rock_bands.find(default_band) {|band| band > "Led Zeppelin"}
    => "Queen"
    
    classic_rock_bands.find(default_band) {|band| band > "ZZ Top"}
    => "ABBA"
    
    # Let's try something a little fancier, and use a lambda this time.
    # The differences between procs and lambdas are very fine -- I suggest
    # you check Eli Bendersky's article for those differences.
    random_band = lambda do
    	fallback_bands = ["Britney Spears", "Christina Aguilera", "Ashlee Simpson"]
    	fallback_bands[rand(fallback_bands.size)]
    end
    
    # Let's give it a try...
    classic_rock_bands.find(random_band) {|band| band > "ZZ Top"}
    => "Britney Spears"
    >> classic_rock_bands.find(random_band) {|band| band > "ZZ Top"}
    => "Ashlee Simpson"
    >> classic_rock_bands.find(random_band) {|band| band > "ZZ Top"}
    => "Christina Aguilera"
    

    To see a “real” application of detect/find's optional argument, see this Ruby Quiz problem.

    Categories
    Uncategorized

    Enumerating Ruby’s “Enumerable” Module, Part 2: “collect”, a.k.a. “map”

    In the last article in this series covering the methods in Ruby’s Enumerable module, I covered all? and any?. In this installment, I’ll look at the collect method, a.k.a. the map method.

    collect, a.k.a. map

    • In plain language: Create an array by performing some operation on every item in the given collection.
    • Ruby.Doc.org’s entry: Enumerable#collect / Enumerable#map
    • Expects: A block containing the operation (it’s optional, but you’re likely to use one most of the time).
    • Returns: An array made up of items created by performing some operation on the given collection.

    collect and map are synonyms — you can use either. I personally prefer map as it’s shorter and makes more sense: I view the operation as using a function to map a collection to an array.

    Using collect/map with Arrays

    When used on an array and a block is provided, collect/map passes each item to the block, where the operation in the block is performed on the item and the result is then added to the result array. Note the the result array has the same number of elements as the given array.

    [1, 2, 3, 4].map {|number| number ** 2}
    => [1, 4, 9, 16]
    
    ["Aqua", "Bat", "Super", "Wonder Wo"].map {|adjective| adjective + "man"}
    => ["Aquaman", "Batman", "Superman", "Wonder Woman"]
    

    When the block is omitted, collect/map uses this implied block: {|item| item}, which means when applied on an array without a block, collect/map is the identity function — the resulting array is the same as the given array.

    [1, 2, 3, 4].map
    => [1, 2, 3, 4]
    
    ["Aqua", "Bat", "Super", "Wonder Wo"].map
    => ["Aqua", "Bat", "Super", "Wonder Wo"]
    

    Using collect/map with Hashes

    When used on a hash and a block is provided, collect/map passes each key/value pair in the hash to the block, which you can “catch” as either:

    1. A two-element array, with the key as element 0 and its corresponding value as element 1, or
    2. 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, where the operation in the block is performed on the item and the result is then added to the result array. Note the the result array has the same number of elements as the given array.

    burgers = {"Big Mac" => 300, "Whopper with cheese" => 450, "Wendy's Double with cheese" => 320}
    
    # What if I had just half a burger?
    burgers.map {|burger| burger[1] / 2}
    => [160, 150, 225]
    
    burgers.map {|sandwich, calories| calories / 2}
    => [160, 150, 225]
    
    burgers.map {|burger| "Have a tasty #{burger[0]}!"}
    => ["Have a tasty Wendy's Double with cheese!", "Have a tasty Big Mac!", "Have a tasty Whopper with cheese!"]
    
    burgers.map {|sandwich, calories| "Have a tasty #{sandwich}!"}
    => ["Have a tasty Wendy's Double with cheese!", "Have a tasty Big Mac!", "Have a tasty Whopper with cheese!"]
    
    burgers.map {|sandwich, calories| ["Half a #{sandwich}", calories / 2]}
    => [["Half a Wendy's Double with cheese", 160], ["Half a Big Mac", 150], ["Half a Whopper with cheese", 225]]
    

    When the block is omitted, collect/map uses this implied block: {|item| item}, which means when applied on an hash without a block, collect/map returns an array containing a set of two-item arrays, one for each key/value pair in the hash. For each two-item array, item 0 is the key and item 1 is the corresponding value.

    burgers = {"Big Mac" => 300, "Whopper with cheese" => 450, "Wendy's Double with cheese" => 320}
    
    burgers.map
    => [["Wendy's Double with cheese", 320], ["Big Mac", 300], ["Whopper with cheese", 450]]
    

    Special Case: Using collect/map on Empty Arrays and Hashes

    When applied to an empty array or hash, with or without a block, all? always returns true.

    [].map
    => []
    
    [].map {|item| item * 2}
    => []
    
    {}.map
    => []
    
    {}.map {|sandwich, calories| "Have a tasty #{sandwich}!"}
    => []
    

    In the Next Installment…

    …the detect (a.k.a. find) method.

    Categories
    Uncategorized

    An Intro to Ruby’s “Enumerable” Module

    “If you’re still writing for loops, stop; there’s a better way.” In Ruby, for loops should be the exception. Make sure you know all about the functionality offered by the Enumerable module, which has methods for iterating through, sorting and searching arrays, hashes and other collection-type objects.