Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access a Ruby enumerator object from within its block

I would like to access the size of the enumerator from within the block, like this:

["cat", "dog", "mouse"].each_with_index { |e, i| puts "#{i + 1} of #{x.size}: #{e}" }

Where x would be the array that runs each_with_index.

I tried the tap method, but that did not work the way I thought it would.

For example, this code seems OK, I guess:

["cat", "dog", "mouse"].tap { |a| a.each_with_index { |e, i| puts "#{i + 1} of #{a.size}: #{e}" }} # Works, but why do I need two blocks?

I was hoping to do it using just one block, not two. Like this:

["cat", "dog", "mouse"].each_with_index.tap { |a, e, i| puts "#{i + 1} of #{a.size}" } # Does not work
like image 615
Glenn Avatar asked Mar 06 '23 00:03

Glenn


2 Answers

That's because when invoking each_with_index, you're able to work within the block, with the two arguments you've given - in this case, the item and its index.

Normally you could store the array separately and then reference it from the block:

animals = ["cat", "dog", "mouse"]
animals.each_with_index { |e, i| puts "#{i + 1} of #{animals.size}: #{e}" }

And it'd work, with two lines of code, and one block, but there you're wouldn't be able to reference just your animals array from within the block.

So your attempt on using tap seems to be the more accurate idea. As tap yields self to the block, that's what you want, and then return self, you're able to perform the operation within your block, as you don't need to do anything else then.

["cat", "dog", "mouse"].tap do |a|
  a.each_with_index do |e, i|
    puts "#{i + 1} of #{a.size}: #{e}"
  end
end

But for this you do need both blocks, one for accessing to self (["cat", "dog", "mouse"]) and then for using each_with_index on self.

like image 59
Sebastian Palma Avatar answered May 09 '23 22:05

Sebastian Palma


Ruby doesn't have such method, but you could built it yourself: (this is for demonstration purposes, you should usually not alter core classes)

class Enumerator
  def with_enum
    each do |*values|
      yield values, self
    end
  end
end

["cat", "dog", "mouse"].each.with_index(1).with_enum do |(e, i), enum|
  puts "#{i} of #{enum.size}: #{e}"
end

Note that enum is a reference to the enumerator, not the array itself. Enumerator doesn't expose the object it is referring to.

like image 23
Stefan Avatar answered May 09 '23 21:05

Stefan