Enumerable: Scratching that Each
This is part of the Back to Basics series.
See the introductory article for an explanation of why this series is important.
Last time, on “Back to Basics: Enumerable”, we learned about how each
works, and how by including Enumerable
on a class with each
we now have access to dozens of new methods.
First, check out the six new versions of each
!:
each_cons(n) { |cons_cell| ... }
each_entry { |entry| ... }
each_slice(n) { |slice| ... }
- We will cover this in a later section.
each_with_index { |item, index| ... }
each_with_object(obj) { |item, obj| ... }
reverse_each { |item| ... }
A “cons cell” is a list primitive from languages like lisp
which is a fixed number of items (2 in lisp
).
each_cons(n) { |cons_cell| ... }
yields a series of these cons cells, each of length n
.
If n is larger than the full list, nothing is yielded.
You can think of this as a “sliding window” down the enumerable, which is n
items wide.
%w(a b c d e f g).each_cons(3) do |notes|
p notes
end
["a", "b", "c"]
["b", "c", "d"]
["c", "d", "e"]
["d", "e", "f"]
["e", "f", "g"]
Remember poor Gollum and his PocketContentsGuesses
from last time?
class PocketContentGuesses
def each
yield "handses"
yield "knife"
yield "string", "nothing"
end
end
PocketContentGuesses.new.each { |item| p item }
"handses"
"knife"
"string"
Notice how "nothing"
is missing from the output?
This is because of how block arguments work in Ruby.
If you don’t provide enough slots in a block definition for all the items yielded, Ruby just fills in the ones it can.
You can use the splat operator to wrap all the arguments in a list
PocketContentGuesses.new.each { |*items| p items }
["handses"]
["knife"]
["string", "nothing"]
You can also use that to just slurp up the remainder.
PocketContentGuesses.new.each do |first, *rest|
p first, rest
end
"handses"
[]
"knife"
[]
"string"
["nothing"]
But we can’t quite get it right for this use case, can we?
each_entry { |entry| ... }
solves this by only wrapping multiple yielded values into a list, while leaving the single yielded values alone.
PocketContentGuesses.new.each_entry { |entry| p entry }
"handses"
"knife"
["string", "nothing"]
Again, never actually write an enumerable like Gollum’s, but this each_entry
can be useful when processing unusual streams of data, or when trying to debug the values being yielded to you.
each_with_index { |obj, index| ... }
should feel like an old friend after the ArrayWithOrdinals
examples at the top.
Note that it is an index, which means zero-based.
%w(a b c).each_with_index do |note, index|
puts "##{index + 1}: #{note}"
end
#1: a
#2: b
#3: c
Out of the flavors of each
that Enumerable
adds, each_with_index
is one of my favorites, especially when chained with other enumerable methods.
Have you ever found yourself writing methods like this?
def interesting_data
arr = []
data.each do |item|
arr << item if item.interesting?
end
arr
end
# Returns: an array of interesting items.
Well, there are several things wrong with that example (and we will revisit this later), but the first is that useless local variable.
each_with_object(obj) { |item, obj| ... }
takes an object and yields it along with each item.
It may seem obvious, but you will normally want a mutable object of some kind, though there are other use cases for this pattern.
def interesting_data
data.each_with_object([]) do |item|
arr << item if item.interesting?
end
end
# Returns: the same array of interesting items as before.
The last each
variation is reverse_each { |item| ... }
, which is well labeled.
Keep in mind: the default implementation creates an array and then walks backward through the array yielding each item.
This can be a little wasteful in terms of time/memory for large Enumerable
objects.
However, most common Enumerable
classes like Array
have an optimized version of this method that directly traverses the data structure.
%w(a b c).reverse_each { |item| puts item }
c
b
a
To read the rest of the series: click here