This is part of the Back to Basics series.
See the introductory article for an explanation of why this series is important.

Last time, on Enumerable: Picking Teams, we learned how to group similar items in an enumerable collection.

This time we are going to cover searching for items and filtering items in a collection.

Here is a quick summary:

For all of the examples below, we will use the following setup code:

require "ostruct"

all_my_friends = [
  {
    first_name: "John",
    last_name: "Doe",
    favorite_color: "brown",
    dominant_hand: "left"
  },
  {
    first_name: "Jane",
    last_name: "Smith",
    favorite_color: "blue",
    dominant_hand: "right"
  },
  {
    first_name: "Jack",
    last_name: "Sparrow",
    favorite_color: "brown",
    dominant_hand: "right"
  },
  {
    first_name: "Jessica",
    last_name: "Rabbit",
    favorite_color: "red",
    dominant_hand: "left"
  },
  {
    first_name: "James",
    last_name: "Hook",
    favorite_color: "red",
    dominant_hand: "left"
  },
  {
    first_name: "Juliet",
    last_name: "Capulet",
    favorite_color: "blue",
    dominant_hand: "right"
  },

].map(&OpenStruct.method(:new))


Let’s say we want to find the first friend whose favorite color is blue.
We can use find { |item| ... }, aka detect to return the first item that returns a truthy value from a block.

p all_my_friends.find { |person| person.favorite_color == 'blue' }
# => #<OpenStruct first_name="Jane", last_name="Smith", favorite_color="blue", dominant_hand="right">


Let’s instead say we wanted to know where in the list our friend James was.
We can use find_index { |item| ... } to return the index of the first time that returns a truthy value from a block.

p all_my_friends.find_index { |person| person.first_name == "James" } # => 4


Alternatively, if we have a reference to an item already, we can also find the index of that item in the list with find_index(item).

juliet = OpenStruct.new(
  first_name: "Juliet",
  last_name: "Capulet",
  favorite_color: "blue",
  dominant_hand: "right"
)
p all_my_friends.find_index(juliet) # => 5

NOTE: this is just using #== to check for the item, it isn’t looking for a specific instance of a class, so if you have duplicate data only the first index will be returned.



Now let’s see who of our friends are left-handed.
find_all { |item| ... }, aka filter or (most commonly) select will return an array of all items in a collection that return a truthy value from the block.

all_my_friends
  .select { |person| person.dominant_hand == "left" }
  .each { |person| puts([person.first_name, person.last_name].join(" ")) }
John Doe
Jessica Rabbit
James Hook


On the other hand, how about the right-handed people?
We can use reject { |item| ... } to return an array of all items in a collection that return a falsey value from a block.

all_my_friends
  .reject { |person| person.dominant_hand == "left" }
  .each { |person| puts([person.first_name, person.last_name].join(" ")) }
Jane Smith
Jack Sparrow
Juliet Capulet

Note that we kept the block the same as the previous example and just swapped the method. We could also have swapped both the logic in the block and the method and ended up with the same list as before.

all_my_friends
  .reject { |person| person.dominant_hand != "left" } # or person.dominant_hand == "right"
  .each { |person| puts([person.first_name, person.last_name].join(" ")) }
John Doe
Jessica Rabbit
James Hook


Let’s say we only want the first friend in the list for each favorite color.
We can use uniq { |item| ... } to return an array of items which return unique values from a block.

all_my_friends
  .uniq { |person| person.favorite_color } # or `.uniq(&:favorite_color)`
  .each { |person| puts "#{person.favorite_color}: #{person.first_name} #{person.last_name}"}
brown: John Doe
blue: Jane Smith
red: Jessica Rabbit

You can also omit the block and it will return an array with no duplicate items (again calling #== on each item).

p %w[a a a a b b b c c d].uniq # => ["a", "b", "c", "d"]


To read the rest of the series: click here