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.
Iterating and Taking
Sometimes you just want to go through your collection, or perhaps even just take a few of them, just a sampling for now.
#first
/ #take
first
allows you to get the first few items of a collection:
[1, 2, 3, 4, 5].first(3)
# => [1, 2, 3]
...or if you use it with one argument it returns the first element:
[1, 2, 3, 4, 5].first
# => 1
Interestingly if you want the first element but still want an Array
returned there's no rule against using 1
for the number of elements:
[1, 2, 3, 4, 5].first(1)
# => [1]
This has been handy for me in the past.
first
is used, as the name suggests, to get the first elements out of a collection. Array
itself also implements last
, being first
s dual method, but that's not Enumerable
specifically.
#take_while
take_while
works a lot like take
except it takes a conditional Block Function rather than a number:
[1, 2, 3, 4, 5].take_while { |v| v < 3 }
# => [1, 2]
It can be useful for when you want to get the first few elements of a list, but up to a certain point. This is much more useful when the collection is sorted.
#drop
drop
is an interesting inversion of first
in that it gives you back a new Array
without the first few elements:
[1, 2, 3, 4, 5].drop(3)
# => [4, 5]
drop
can be handy if you want to ignore the first few elements of a collection.
#drop_while
drop_while
works a lot like drop
, just the same as take_while
and take
, except it drops elements up to that point:
[1, 2, 3, 4, 5].drop_while { |v| v < 3 }
# => [3, 4, 5]
As with take_while
this can be much more useful with a sorted collection.
#each_entry
On the surface each_entry
looks like an alias for each
. Almost, but not quite:
class Foo
include Enumerable
def each
yield 1
yield 1, 2
yield
end
end
Foo.new.each_entry { |o| p o }
# STDOUT: 1
# STDOUT: [1, 2]
# STDOUT: nil
# => Foo<>
Looking at the example on the doc site it's doing a few different things:
Foo.new.each { |o| p o }
# STDOUT: 1
# STDOUT: 1
# STDOUT: nil
# => nil
Notice two things. The second yield only has one element now, and the return value is nil
rather than the calling object Foo
. nil
because the original implementation of each
for Array
returns the base object.
I have not found a use for each_entry
, but will endeavor to find what it might be used for. In the mean time I would encourage the use of each
instead.
#each_with_index
each_with_index
, as the name implies, is each
with an index along for the ride:
[1, 2, 3].each_with_index { |v, i| p v + i }
# STDOUT: 1
# STDOUT: 3
# STDOUT: 5
# => [1, 2, 3]
While that can be useful on its own it returns an Enumerator
when not given a Block Function, meaning we can chain onto it:
[1, 2, 3].each_with_index.map { |v, i| v + i }
# => [1, 3, 5]
each_with_index
is useful when we want to iterate along with the index, especially if we need that index for something.
#reverse_each
reverse_each
iterates in the reverse order:
[1, 2, 3].reverse_each { |v| p v }
# STDOUT: 3
# STDOUT: 2
# STDOUT: 1
# => [1, 2, 3]
Since it returns an Enumerator
like most of these methods do should you not pass a Block Function, you can also chain it:
[1, 2, 3].reverse_each.map { |v| v * 2 }
# => [6, 4, 2]
reverse_each
is useful when you want to go backwards, and there will be cases where you want to do that.
Wrapping Up
The next few articles will be getting into the various parts of Enumerable
, grouped by functionality:
TransformingPredicate ConditionsSearching and FilteringSorting and ComparingCountingGroupingCombiningIterating 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 (1)
Small typo,
"if you use it with one argument it returns the first element:"
should be like this
"if you use it without argument it returns the first element:"