Once again, it’s Enumerating Enumerable, my series of articles in which I attempt to outdo Ruby-Doc.org’s documentation of Ruby’s Enumerable
module. In this article, I cover the find_index
method, which was introduced in Ruby 1.9.
In case you missed any of the previous articles, they’re listed and linked below:
- all?
- any?
- collect / map
- count
- cycle
- detect / find
- drop
- drop_while
- each_cons
- each_slice
- each_with_index
- entries / to_a
- find_all / select
Enumerable#find_index Quick Summary
In the simplest possible terms | What’s the index of the first item in the collection that meets the given criteria? |
---|---|
Ruby version | 1.9 |
Expects | A block containing the criteria. |
Returns |
|
RubyDoc.org’s entry | Enumerable#find_index |
Enumerable#find_index and Arrays
When used on an array, find_index
passes each item in the array to the given block and either:
- Stops when the current item causes the block to return a value that evaluates to
true
(that is, anything that isn’tfalse
ornil
) and returns the index of that item, or - Returns
nil
if there is no item in the array that causes the block to return a value that evaluates totrue
.
Some examples:
# How about an array of the name of the first cosmonauts and astronauts, # listed in the chronological order of the missions? mission_leaders = ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter", "Nikolayev", "Popovich"] => ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter", "Nikolayev", "Popovich"] # Yuri Gagarin was the first in space mission_leaders.find_index{|leader| leader == "Gagarin"} => 0 # John Glenn was the fifth mission_leaders.find_index{|leader| leader == "Glenn"} => 4 # And James Tiberius Kirk is not listed in the array kirk_present = mission_leaders.find_index{|leader| leader == "Kirk"} => nil
Enumerable#find_index and Hashes
When used on a hash, find_index
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.
As with arrays, find_index
:
- Stops when the current item causes the block to return a value that evaluates to
true
(that is, anything that isn’tfalse
ornil
) and returns the index of that item, or - Returns
nil
if there is no item in the array that causes the block to return a value that evaluates totrue
.
Some examples:
require 'date' => true # These are the names of the first manned spaceships and their launch dates launch_dates = {"Kedr" => Date.new(1961, 4, 12), "Freedom 7" => Date.new(1961, 5, 5), "Liberty Bell 7" => Date.new(1961, 7, 21), "Orel" => Date.new(1961, 8, 6), "Friendship 7" => Date.new(1962, 2, 20), "Aurora 7" => Date.new(1962, 5, 24), "Sokol" => Date.new(1962, 8, 11), "Berkut" => Date.new(1962, 8, 12)} => {"Kedr"=>#<Date: 4874803/2,0,2299161>, "Freedom 7"=>#<Date: 4874849/2,0,2299161>, "Liberty Bell 7"=>#<Date: 4875003/2,0,2299161>, "Orel"=>#<Date: 4875035/2,0,2299161>, "Friendship 7"=>#<Date: 4875431/2,0,2299161>, "Aurora 7"=>#<Date: 4875617/2,0,2299161>, "Sokol"=>#<Date: 4875775/2,0,2299161>, "Berkut"=>#<Date: 4875777/2,0,2299161>} # Where in the list is John Glenn's ship, the Friendship 7? launch_dates.find_index{|ship, date| ship == "Friendship 7"} => 4 # Where in the list is the first mission launched in August 1962? launch_dates.find_index{|ship, date| date.year == 1962 && date.month == 8} => 6 # The same thing, expressed a little differently launch_dates.find_index{|launch| launch[1].year == 1962 && launch[1].month == 8} => 6
Using find_index as a Membership Test
Although Enumerable
has a method for checking whether an item is a member of a collection (the include?
method and its synonym, member?
), find_index
is a more powerful membership test for two reasons:
include?
/member?
only check membership by using the==
operator, whilefind_index
lets you define a block to set up all sorts of tests.include?
/member?
asks “Is there an object X in the collection equal to my object Y?” whilefind_index
can be used to ask “Is there an object X in the collection that matches these criteria?”include?
/member?
returnstrue
if there is an object X in the collection that is equal to the given object Y.find_index
goes one step further: not only can it be used to report the equivalent oftrue
if there is an object X in the collection that is equal to the given object Y, it also reports its location in the collection.
A quick example of this use in action:
# Once again, the mission leaders mission_leaders = ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter", "Nikolayev", "Popovich"] => ["Gagarin", "Shepard", "Grissom", "Titov", "Glenn", "Carpenter", "Nikolayev", "Popovich"] # Yuri Gagarin is in the list gagarin_in_list = mission_leaders.find_index {|leader| leader == "Gagarin"} => 0 # Captain James T. Kirk is not kirk_in_list = mission_leaders.find_index {|leader| leader == "Kirk"} => nil # gagarin_in_list is 0, which as a non-false and non-nil value evaluates as true. # We can use it as both a membership test *and* as his location in the list. p "Gagarin's there. He's number #{gagarin_in_list + 1} in the list." if gagarin_in_list "Gagarin's there. He's number 1 in the list." => "Gagarin's there. He's number 1 in the list." # kirk_in_list is nil, which is one of Ruby's two "false" values. # Let's use it with the "something OR something else" idiom that # many Ruby programmers like. kirk_in_list || (p "You only *think* he wasn't there.") "You only *think* he wasn't there." => "You only *think* he wasn't there."
Parts that Haven’t Been Implemented Yet
Ruby-Doc.org’s documentation is generated from the comments in the C implementation of Ruby. It mentions a way of calling find_index
that is just like calling include?
/member?
:
# What the docs say (does not work yet!) ["Alice", "Bob", "Carol"].find_index("Bob") => 1 # What happens with the current version of Ruby 1.9 ["Alice", "Bob", "Carol"].find_index("Bob") ArgumentError: wrong number of arguments(1 for 0) ...
Ruby 1.9 is considered to be a work in progress, so I suppose it’ll get implemented in a later release.
One reply on “Enumerating Enumerable: Enumerable#find_index”
[…] functional niceties), but I have no excuse for blowing this. I’ve actually written a whole series of articles on the power of Ruby’s Enumerable module, including the select […]