DEV Community

Cover image for Understanding Ruby - Enumerable - Iterating and Taking
Brandon Weaver
Brandon Weaver

Posted on

Understanding Ruby - Enumerable - Iterating and Taking

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.

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

...or if you use it with one argument it returns the first element:

[1, 2, 3, 4, 5].first
# => 1
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

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

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

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

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

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:

  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 (1)

Collapse
dalef profile image
Dale Flannery

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:"