Introduction
Enumerable. Debatably one of, if not the, most powerful features in Ruby. As a majority of your time in programming is dealing with collections of items it's no surprise how frequently you'll see it used.
Difficulty
Foundational
Some knowledge required of functions in Ruby. This post focuses on foundational and fundamental knowledge for Ruby programmers.
Prerequisite Reading:
- Understanding Ruby - Blocks, Procs, and Lambdas
- Understanding Ruby - to_proc and Function Interfaces
- Understanding Ruby - Triple Equals
- Understanding Ruby - Comparable
Enumerable
Enumerable is an interface module that contains several methods for working with collections. Many Ruby classes implement the Enumerable interface that look like collections. Chances are if it has an each method it supports Enumerable, and because of that it's quite ubiquitous in Ruby.
Note: This idea was partially inspired by Lamar Burdette's recent work on Ruby documentation, but takes its own direction.
Transformations
Our first section will focus on methods which transform collections into new collections, primarily by using Block Functions.
#map / #collect
map expresses the idea of transforming a collection using a function, or by using the english word expressing a way to get from point A to point B. Amusingly in some functional programming languages this is expressed A -> B, wherein -> is the function.
For us it might be used something like this:
[1, 2, 3].map { |v| v * 2 }
# => [2, 4, 6]
In which the function is to double every element of a collection, giving us back a brand new collection in which all elements are doubles of the original.
Using the syntax for Symbol#to_proc we can also use map to extract values out of objects:
people.map(&:name)
If we had an Array of people we could use map to get all of their names using this shorthand.
map is great for transforming collections and pulling things out of a collection.
#flat_map / #collect_concat
flat_map will both map a collection and afterwards flatten it:
hands = [
Hand.from_str('S2, S3, S4'),
Hand.from_str('S3, S4, S5'),
Hand.from_str('S4, S5')
]
hands.flat_map(&:cards).map(&:to_s).join(', ')
# => "S2, S3, S4, S3, S4, S5, S4, S5"
flat_map is great when you want to extract something like an Array from items and combine them all into one Array. It's also great for generating products, but remember that Ruby also has the Array#product method which works better unless you have something more involved to do.
It's for when you want one Array rather than Arrays of Arrays.
#filter_map
filter_map is interesting in that it combines the idea of filter and the idea of map. If the function passed to filter_map returns something falsy (false or nil) it won't be present in the returned collection:
[1, 2, 3].filter_map { |v| v * 2 if v.odd? }
# => [2, 6]
In this case 2 will be ignored. filter_map is great if you find yourself using map, returning nil, and using compact at the end to drop nil values.
This method is great when you want to both filter down a collection and do something with those values.
Wrapping Up
The next few articles will be getting into the various parts of Enumerable, grouped by functionality:
Transforming- Predicate Conditions
- Searching and Filtering
- Sorting and Comparing
- Counting
- Grouping
- Combining
- Iterating and Taking
- Coercion
While lazy is part of Enumerable that deserves a post all its own, and we'll be getting to that one soon too.
Want to keep up to date on what I'm writing and working on? Take a look at my new newsletter: The Lapidary Lemur
Top comments (2)
Minor comment around the one example...
#join(doc) calls#to_son the object, so that extra map is not useful.True, and easy to forget it does that. It also flattens collections: