Intro

These are methods that allow us to iterate over collections and conduct various tasks such as transforming, searching, mapping, ands selecting subsets from our collections. Enumerable methods make these iteration patterns easier for us.

Selecting from a collection

Imagine you want to select all elements from a list except a certain element. The .select() method takes an element variable and a condition and modifies the list. Using the |element| <condition> syntax within the select method. This is |element| is known as a blockVariable this is the element the iterator of the select method (and other enumerable methods) yield on each iteration, so on the first it’ll be the first element of the collection. The Block variable can be anything, can be a simple ‘x’ but that’s bad so use appropriate naming conventions.

fruit = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruit.select(|fruit| fruit != "Melon") # -> ["Orange", "Apple", "Grape","Mango", "Pineapple", "Papaya"]

Or better yet we can use the reject() method

fruit = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruit.reject(|fruit| fruit == "Melon") # -> ["Orange", "Apple", "Grape","Mango", "Pineapple", "Papaya"]

This gave use the same result because the reject method is the opposite of the select method…

The each method

A very versatile method that allows us to do lots of things. It’s rather similar to map methods in other languages, as it iterates through a collection and yields the element to a code block to do something with it. The each method returns the ORIGINAL array it’s called upon as a return value and not nil.

fruit = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruit.each(|fruit| puts "#{fruit} is a yummy fruit")
  • The code block is appending “is a yummy fruit” to the element name as an argument for the puts function, naturally we can more than this but this is a good example…
  • If the block is longer than a single line and is an actual block then we use do | element| syntax.
fruit = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruit.each do |fruit|
	fruit.upcase
	puts "#{fruit} is a yummy fruit"

The each method also works with hashes and on each iteration yields both the key and value

dis_hash = {:one => 1, :two => 2}
dis_hash.each(|key, value| puts "#{key} yields the value #{value} in dis_hash.")

Each with index

The same as the each method but yields two block variables one for the element and the other for its index in the collection. For example:

fruit = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruit.each_with_index(|fruit, index| puts "#{fruit} is a yummy fruit and its index is #{index}"

Just like the each method this also returns the original array it’s called upon.

The map method

Similar to the each method, but the map method applies the functionality of the code block to the element and returns a new array of the modified values. It’s also known as the collect method… //not sure why or in what context tbh.

fruit = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruit.map(|fruit| fruit.upcase) 

Another example using gsub to substitute a string

my_order = ['medium burger', 'medium fries', 'medium milkshake']
 
my_order.map { |item| item.gsub('medium', 'extra large') } #=> ["extra large burger", "extra large fries", "extra large milkshake"]
 

The reduce method

This method takes an array or hash and iterates over all values within to produce a single object… How? A classic example includes a sum of an array

numbers = [1,2,3,4]
numbers.reduce(|num, sum| sum += num) # => 10

Everything looks standard thus far but I’ve introduced an accumulator. This is a variable that stores a value as an iterator iterates over a collection and is then returned as the return value. In the previous example sum ‘accumulates’ the sum value of the iterations. By default the initial value of the accumulator is the first element in a collection, so it goes 1 + 2 + 3 + 4 = 10.

To set an initial value for the accumulator, pass it as an argument to the reduce method. Like so

my_numbers = [5, 6, 7, 8]
my_numbers.reduce(1000) { |sum, number| sum + number } #=> 1026

A more complex example including hashes (voting on a restaurant)

votes = ["Bob's Dirty Burger Shack", "St. Mark's Bistro", "Bob's Dirty Burger Shack"]
 
votes.reduce(Hash.new(0)) do |result, vote|
  result[vote] += 1
  result
end
#=> {"Bob's Dirty Burger Shack"=>2, "St. Mark's Bistro"=>1}

defaultRubyHashValues we passed in a much more interesting initial value for our accumulator this time. When we pass in an argument to Hash.new, that becomes the default value when accessing keys that do not exist in the hash. Example:

names = Hash.new("Bob")
names[:first] #=> "Bob"
names[:second] #=> "Bob"

But when a value is assigned…

names[:first] #=> "Bob"
names[:second] #=> "Bob"
names[:first] = "Jon"
 
names[:first] #=> "Jon"
names[:second] #=> "Bob"

Bang methods

Enumerables like #map and #select return new arrays but don’t modify the arrays that they were called on. However, if you want to mutate the original collection and the elements within we need to use BangMethods.

fruits = ["Orange", "Apple", "Grape", "Melon", "Mango", "Pineapple", "Papaya"]
fruits.map!(|fruit| fruit.upcase)
 
fruits #=> ["ORANGE", "APPLE", "GRAPE", "MELON", "MANGO", "PINEAPPLE","PAPAYA"]

Now the original array is mutated permanently… As you’ll recall, bang methods are identified by their exclamation marks (!) at the end of their name. All bang methods are destructive and modify the object they are called on.

So if you want to reuse the result of an enumerable without using bang methods simply assign the return value to a variable or wrap it in a method definition. //But I think the prior is best, since there’s no point wrapping a pre-existing method with a method…