Ruby Search Enumerators

Objectives

  1. Understand return values for enumerators.
  2. Use a truthy or falsey evaluation in a block.
  3. Use #select to select matching elements from a collection based on a block.
  4. Use #detect to find a matching element from a collection based on a block.
  5. Use #reject to filter matching elements from a collection based on a block.

Overview

Every method in ruby must return a value. When we iterate or enumerate over a collection with #each, the return value is always the original collection. This is an example of a static return value, no matter what we do with #each, it will always return the same object that received the call to #each.

["Red", "Yellow", "Blue"].each do |color|
  puts "There are #{color.length} letters in #{color}"
end #=> ["Red", "Yellow", "Blue"]

Often we want to search for elements in a collection based on a condition. Imagine wanting to find all even numbers in a collection of numbers using #each.

matches = []
[1,2,3,4,5].each do |i|
  matches << i if i.even? # add i to the matches array if it is even
end #=> [1,2,3,4,5]
matches #=> [2,4]

Implementing a selection routine with a low-level enumerator like #each is costly in a few ways.

  1. We have to hang on to the matches within the local array matches. Programmers use the phrase maintain state to refer to this task. Cars can be in a state like "Reverse, Drive, Neutral". Our matches array has states of "Empty, [2], [2,4]".
  2. Our block is complicated with conditional logic that can be implicit with a better enumerator.
  3. Our code lacks intention and clear semantics. If we mean, #find_all or #select, why don't we just say that?

#select

When you evoke #select on a collection, the return value will be a new array containing all the elements of the collection that cause the block passed to #select to return true. That means for each iteration, if the block evaluates to true, the element yielded to that iteration will be kept in the return value array.

[1,2,3,4,5].select do |number|
  number.even?
end #=> [2,4]

In the first iteration of the block above, number will be assigned the value 1. Because 1.even? will return false, 1 will not be in the return array for this call to #select (same for 3 and 5). In the second iteration, number will be 2. Because 2.even? will return true, 2 will be in the return array (same for 4).

You can see the clarity and expressiveness of this syntax in the short block from below.

[1,2,3,4,5].select{|i| i.odd?} #=> [1,3,5]

[1,2,3].select{|i| i.is_a?(String)} #=> []

Notice that if no element makes the block evaluate to true, an empty array is returned.

#detect or #find

NOTE: detect and find are two names for the same method. For every example below we'll use detect, but you can use them interchangeably.

Whereas #select will return all elements from the original collection that cause the block to evaluate to true, #detect will only return the first element that makes the block true.

[1,2,3].detect{|i| i.odd?} #=> 1
[1,2,3].find{|i| i.odd?} #=> 1

As you can see, even though both 1 and 3 would cause the block to evaluate to true, because 1 is first in the array, it alone is returned.

[1,2,3,4].detect{|i| i.even?} #=> 2
[1,2,3,4].detect{|i| i.is_a?(String)} #=> nil

Notice also that #detect will always return a single object where #select will always return an array.

#reject

#reject will return an array with the elements for which the block is false.

[1,2].reject{|i| i.even?} #=> [1]

Conclusion

#select, #detect, and #reject are part of a family of search and filter type enumerators whose purpose is to help you refine a collection to only matching elements. They are way easier to manage than using lower-level methods like #each and create meaningful return values based on expressions in a block.

Video Review

View Search Enumerators on Learn.co and start learning to code for free.

Unlock your future in tech
Learn to code.

Learn about Flatiron School's Mission

With a new take on education that falls somewhere between self-taught prodigy and four-year computer science degree, the Flatiron School promises to turn students with little programming experience into developers.

In the six months since the Manhattan coding school was acquired by WeWork, it has spawned locations in Washington, D.C., Brooklyn, and London. Now, WeWork is opening a fourth Flatiron School location, this time in Houston.

Adam Enbar, Flatiron School's cofounder, believes now is the time to grow. "How the world is changing has impacted working and learning in very similar ways. We think education fundamentally is about one thing: enabling people to pursue a better life."

Learn. Love. Code.
Students come to Flatiron School to change their lives. Join our driven community of career-changers and master the skills you need to become a software engineer or a data scientist.
Find Us