DEV Community

loading...
Cover image for Understanding Ruby - Enumerable - Transformations

Understanding Ruby - Enumerable - Transformations

Brandon Weaver
Staff Eng / Ruby Lead / Global Neurodiversity Chair at @Square. Autistic / ADHD, He / Him. I'm the Lemur guy.
・3 min read

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:

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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:

  1. Transforming
  2. Predicate Conditions
  3. Searching and Filtering
  4. Sorting and Comparing
  5. Counting
  6. Grouping
  7. Combining
  8. Iterating and Taking
  9. 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

Discussion (2)

Collapse
grumbleafrican profile image
grumbleafrican • Edited

Minor comment around the one example...

hands.flat_map(&:cards).map(&:to_s).join(', ')
# => "S2, S3, S4, S3, S4, S5, S4, S5"
Enter fullscreen mode Exit fullscreen mode

#join (doc) calls #to_s on the object, so that extra map is not useful.

irb(main):001:0> [:a, :b, :c].join(',')
=> "a,b,c"
Enter fullscreen mode Exit fullscreen mode
Collapse
baweaver profile image
Brandon Weaver Author

True, and easy to forget it does that. It also flattens collections:

[[1], 2, 3, [[[4, 5], 6], 7]].join(', ')
# => "1, 2, 3, 4, 5, 6, 7"
Enter fullscreen mode Exit fullscreen mode