Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Ruby, what does it mean to return an 'enumerable'

Tags:

ruby

I am trying to understand the following Ruby code:

digits.each_with_index.inject(0) do |decimal, (digit, index)|
      decimal + digit * 2**index
    end

(For reference, digits is a method that returns an array in which each element is an integer).

The part of the code that is confusing to me is .each_with_index.inject(0). I know what the each_with_index method does, and I know what the inject method does, but I am not sure how the chaining of both together is working. What exactly is going on?

I tried to look at the documentation for each_with_index, and I guess the part that I am having trouble with is the following: "If no block is given, returns an enumerator."

I guess what all of this boils down to is, what is an enumerator?

like image 905
Steven L. Avatar asked Oct 20 '22 13:10

Steven L.


2 Answers

An Enumerable is something the you can loop through. It is included in Array, Set etc.

From the docs: http://ruby-doc.org/core-2.2.2/Enumerable.html

The Enumerable mixin provides collection classes with several traversal and searching methods, and with the ability to sort. The class must provide a method each, which yields successive members of the collection. If Enumerable#max, #min, or #sort is used, the objects in the collection must also implement a meaningful <=> operator, as these methods rely on an ordering between members of the collection.

An Enumerator is something that you can use later to iterate through: http://ruby-doc.org/core-2.1.5/Enumerator.html An Enumerator, is a wrapper class which includes all the methods of Enumarable

In your example code, you're injecting a default value of 0, into the each_with_index loop, so on the first loop decimal is equal to 0, digit is the first value of the array and index is 0. The second time through the loop, decimal is set to the return value of the first pass, digit is your second array value and index is 1.

For example:

digits = [20,30,40]

First time in the loop: decimal = 0, digit = 20, index = 0. This then returns 20.

Second time in the loop: decimal = 20, digit = 30, index = 1. This then returns 80.

Third time in the loop: decimal = 80, digit = 40, index =2. This then returns 240.

So this block returns 240.

like image 128
Yule Avatar answered Oct 22 '22 09:10

Yule


An Enumerator abstracts the idea of enumeration so that you can use all of the handy Enumerable methods without caring what the underlying data structure is.

For example, you can use an enumerator to make an object that acts kind of like an infinite array:

squares = Enumerator.new do |yielder|
  x = 1
  loop do
    yielder << x ** 2
    x += 1
  end
end

squares.take(10)
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
squares.count
# don't wait up for this one!

The cool thing about enumerators is that they are enumerable themselves and most Enumerable methods return enumerators if you don't give them a block. This is what allows you to chain method calls to get one big enumerator.

Here's how I would code each_with_index so that it can be chained nicely:

class Array
  def my_each_with_index &blk
    e = Enumerator.new do |yielder|
      i = 0
      each do |x|
        yielder << [x, i]
        i += 1
      end
    end

    return e unless blk
    e.each(&blk)
  end
end

[3,2,1].my_each_with_index { |x, i| puts "#{i}: #{x}" }
# 0: 3
# 1: 2
# 3: 1

So first we create an enumerator which describes how to enumerate with indices. If no block is given, we simply return the enumerator. Otherwise we tell the enumerator to enumerate (which is what each does) using the block.

like image 28
Max Avatar answered Oct 22 '22 10:10

Max