Enumerator#each Restarts Sequence - ruby

I'm surprised that Enumerator#each doesn't start off at the current position in the sequence.
o = Object.new
def o.each
yield 1
yield 2
yield 3
end
e = o.to_enum
puts e.next
puts e.next
e.each{|x| puts x}
# I expect to see 1,2,3 but I see 1,2,1,2,3
# apparently Enumerator's each (inherited from Enumerable) restarts the sequence!
Am I doin' it wrong? Is there a way to maybe construct another Enumerator (from e) that will have the expected each behavior?

You're not doing it wrong, that's just not the semantics defined for Enumerator#each. You could make a derivative enumerator that only iterates from current position to end:
class Enumerator
def enum_the_rest
Enumerator.new { |y| loop { y << self.next } }
end
end
o = Object.new
def o.each
yield 1
yield 2
yield 3
end
e = o.to_enum
=> #<Enumerator: ...>
e.next
=> 1
e2 = e.enum_the_rest
=> #<Enumerator: ...>
e2.each { |x| puts x }
=> 2
=> 3
And, BTW, each doesn't restart the sequence, it just always runs over the entire span. Your enumerator still knows where it is in relation to the next next call.
e3 = o.to_enum
e3.next
=> 1
e3.next
=> 2
e3.map(&:to_s)
=> ["1", "2", "3"]
e3.next
=> 3

Enumerator#next and Enumerator#each work on the object differently. Per the documentation for #each (emphasis mine):
Iterates over the block according to how this Enumerable was constructed. If no block is given, returns self.
So #each always behaves based on the original setup, not on the current internal state. If you quickly peak at the source you'll see that rb_obj_dup is called to setup a new enumerator.

Related

Ruby: How to use .each to iteration through an array?

In the card game bridge, four cards are given point values: Jack: 1, Queen: 2, King: 3, Ace: 4. Given an array of strings corresponding to a hand of cards (the cards are represented like so: ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]), return the total number of high card points for that hand.
I can solve this simple problem with while loop, but I would like to learn how to use .each to iterate through an array as well, Here is my code that doesn't work
def high_card_points(hand)
sum = 0
hand.each do |i|
if hand[i] == "J"
sum += 1
elsif hand[i] == "Q"
sum += 2
elsif hand[i] == "K"
sum += 3
elsif hand[i] == "A"
sum += 4
end
end
sum
end
Now, when I run it, the error no implicit conversion of String into Integer comes out. How should I do it in the right way?
The problem here, is that when you use each the variable inside the block, is the object inside the array not the index, so you can work as follow:
def high_card_points(hand)
sum = 0
hand.each do |card|
if card == "J"
sum += 1
elsif card == "Q"
sum += 2
elsif card == "K"
sum += 3
elsif card == "A"
sum += 4
end
end
sum
end
and if you execute in pry
[5] pry(main)* => :high_card_points
[6] pry(main)> high_card_points(cards)
=> 10
You can also work whit the index like with each_index. But you can also take an other object-functional aproach:
You can create your class or monkeypatch the class string:
class String
def card_points
case self
when 'J'
1
when 'Q'
2
when 'K'
3
when 'A'
4
else
0
end
end
end
Then proceed like this:
[31] pry(main)> cards.map(&:card_points).inject(0, :+)
=> 10
The error message states, "TypeError (no implicit conversion of String into Integer)" and that the exception was raised in the line hand[i] == "J". The first element passed to the block by each and assigned to the block variable i is i = hand.first #=> "2". We therefore have hand["2"] == "J", or in fact, hand.[]("2"), but the method Array#[] requires its argument to be an integer, and there is "no implicit conversion of String into Integer".
Let me now address a different aspect of your question.
arr = ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]
You could write the following.
arr.reduce(0) do |tot, s|
tot +
case s
when "J" then 1
when "Q" then 2
when "K" then 3
when "A" then 4
else 0
end
end
#=> 10
I can hear you. You are saying, "I said I wanted to use .each!". Well, you have! Let me explain.
arr is an instance of the class Array. Array has Module#include'd the module Enumerable, which is why we can invoke the instance method Enumerable#reduce on arr. (Array.included_modules #=> [Enumerable, Kernel]).
Like all other instance methods in Enumerable, Enumerable#reduce (aka inject) requires a receiver that is an instance of the class Enumerator, but arr is an instance of Array, not Enumerator. Ruby gets around this as follows. When reduce is invoked on arr, she sees that arr is not an instance of Enumerator so she checks to see if arr has a method each (that is, whether arr's class Array has an instance method each). It does, so she invokes each on arr to obtain
enum = arr.each
#=> #<Enumerator: ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J",
# "Q", "K", "A"]:each>
We now have the enumerator on which reduce can be invoked:
enum.reduce(0) do |tot, s|
tot +
case s
when "J" then 1
when "Q" then 2
when "K" then 3
when "A" then 4
else 0
end
end
#=> 10
You don't see Array#each being invoked, but it certainly is. We can confirm that by including Enumerable in a class that does not have a method each and see what happens.
class C
include Enumerable
end
c = C.new
#=> #<C:0x0000000002a118a8>
c.reduce {}
#=> NoMethodError (undefined method `each' for #<C:0x0000000002a118a8>)
class C
def each
end
end
c.reduce {}
#=> nil
This is why every class that includes Enumerable must have an instance method each that returns an enumerator and why each is invoked on instances of that class before an instance method from Enumerable is called.

Ruby splat operator changing value inside loop

I want to define a method which can take an optional amount of arguments and hashes, like so
def foo(*b, **c)
2.times.map.with_index { |i|
new_hash, new_array = {}, b
c.map { |key, value| new_hash[key] = value[i] unless value[i].nil? }
new_array << new_hash if new_hash.length > 0
send(:bar, new_array)
}
end
def bar(*b)
p b
end
If I've understood the splat and double splat operators correctly (which I doubt), then this should send the array b to the bar method, and only adding the new_hash from foo if it contains something. However, something weird happens - I'll try and illustrate with some snippets below
# invoking #foo
foo(a, key: 'value')
# first iteration of loop in #foo
# i is 0
# b is []
# c is { :key => ['value1'] }
# send(:bar, new_array) => send(:bar, [{:key => 'value1'}])
# bar yields: [{:key => 'value1'}]
Now, however, something happens
# second iteration of loop in #foo
# i is 1
# b is [:key => 'value1'] <---- why?
# c is { :key => ['value1']
Why has the value of b changed inside the loop of foo?
edit Updated the code to reflect a new array is created for each iteration
new_hash, new_array = {}, b
This doesn't create a copy of b. Now new_array and b point to the same object. Modifying one in-place will modify the other.
new_array << new_hash
That modifies new_array (and thus b) in place, so the new element remains on the next iteration. Use something like +, which creates a copy:
send(:bar, *(b + (new_hash.empty? ? [] : [new_hash])))

Why does this use of the Hash#each method work only when I remove the splat operator from the parameter?

I'm going through a problem on Ruby Monk, https://rubymonk.com/learning/books/1-ruby-primer/problems/155-restaurant#solution4804
Their solution is great; I like it and it's more compact than mine. Problem is for mine, I just don't understand why it only works when I remove the splat operator from the cost parameter orders. Even if I shouldn't be doing it this way, I'm struggling to figure out what's up. I know sometimes it's unnecessary to understand everything, and it's best to just move on.. but curious.
Here is mine:
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
total_cost = 0
orders.each do |item, number|
total_cost += #menu[item] * number
end
end
menu = {:rice => 3, :noodles => 2}
orders = {:rice => 1, :noodles => 1}
eat = Restaurant.new(menu)
puts eat.cost(orders)
Edit:
To include their suggested solution below
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) {|cost, key| cost + #menu[key]*order[key] }
end
end
end
Edit:
To clear up and answer my own question in the comment
I tried these experiments and it shows inject "removing" the array brackets that splat "put on". Perhaps not the most proper way to think about it? It does help clear up my confusion.
order = { :rice => 1, :noodles => 1 }
menu = { :rice => 3, :noodles => 2 }
[order].inject(0) do |bla, blu|
p bla #=> 0
p blu #=> {:rice=>1, :noodles=>1}
p blu.keys #=> [:rice, :noodles]
end
When you write:
def cost(*orders)
end
then all the parameters passed to the cost method will be put into a single array named orders. These two are thus equivalent:
def cost(*orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost(1,2,3)
def cost(orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost( [1,2,3] ) # note the array literal brackets
In your case, when you remove the "splat" you are saying "set orders to reference whatever was passed in directly". In this case you're passing it a Hash, and when you iterate a hash you get key/value pairs for each entry. This is just what you want.
When you do have the splat, though, you're getting this:
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
So you're wrapping your hash in an array, and then iterating over the elements of the array. Thus, the first value passed to the block is the entire hash, and there is no second parameter.
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
orders.each do |item,number|
p item #=> {:rice=>1, :noodles=>1}
p number #=> nil
end
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
At this point you can't multiply anything by nil and so your code breaks.

Clone an Enumerator in Ruby?

I have a tree that I'm trying to traverse. As I traverse it, I keep a stack of enumerators in which each enumerator is used to enumerate over a tree's children.
I'd like to be able to duplicate this stack of enumerators and hand it to another object so it may traverse the tree starting in the place indicated by the state of the stack.
When I attempt to call #dup on Enumerator, I get an error. Is it possible to duplicate an Enumerator? If not, how could I accomplish the same thing? (I've considered a stack of integers as indices, but am worried about the efficiency.
Here's some code to show what I'm seeing...
Once the first enumerator has started, you cannot duplicate it. That is my situation.
a = [1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>
a.next
=> 1
b = a.dup
TypeError: can't copy execution context
from (irb):3:in `initialize_copy'
from (irb):3:in `initialize_dup'
from (irb):3:in `dup'
from (irb):3
Implement your own enumerator class.
There’s not much magic to an enumerator beyond incrementing an internal counter.
class MyArrayEnumerator
def initialize(array)
#ary,#n=array,0
end
def next
raise StopIteration if #n == #ary.length
a=#ary[#n];#n+=1;a
end
end
class Array
def my_each
MyArrayEnumerator.new(self)
end
end
a = [1,2,3].my_each # => #<MyArrayEnumerator:0x101c96588 #n=0, #array=[1, 2, 3]>
a.next # => 1
b = a.dup # => #<MyArrayEnumerator:0x101c95ae8 #n=1, #array=[1, 2, 3]>
a.next # => 2
b.next # => 2
Use clone instead:
e1 = [1,2,3].each
e1.dup # TypeError: can't copy execution context
e2 = e1.clone
e1.next #=> 1
e2.next #=> 1
keep one "head" amongst Enumerator's instances, and store history for behind copies:
class Enum
def initialize()
#history = [] # history will be shared between instances
#history_cursor = -1
#head = Enumerator.new do |yielder|
#yielder = yielder
enumerate
end
end
def next
if #history_cursor < #history.count - 1
#history[#history_cursor += 1]
else
new_item #head.next
end
end
private
def new_item item
#history << item
#history_cursor = #history.count - 1
item
end
def enumerate
13.times do |i|
#yielder << i # yielder is shared between instances
end
end
end
Usage:
enum1 = Enum.new
p enum1.next # 0
enum2 = enum1.clone
p enum2.next # 1
p enum1.next # 1

Are there something like Python generators in Ruby?

I am new to Ruby, is there a way to yield values from Ruby functions? If yes, how? If not, what are my options to write lazy code?
Ruby's yield keyword is something very different from the Python keyword with the same name, so don't be confused by it. Ruby's yield keyword is syntactic sugar for calling a block associated with a method.
The closest equivalent is Ruby's Enumerator class. For example, the equivalent of the Python:
def eternal_sequence():
i = 0
while True:
yield i
i += 1
is this:
def eternal_sequence
Enumerator.new do |enum|
i = 0
while true
enum.yield i # <- Notice that this is the yield method of the enumerator, not the yield keyword
i +=1
end
end
end
You can also create Enumerators for existing enumeration methods with enum_for. For example, ('a'..'z').enum_for(:each_with_index) gives you an enumerator of the lowercase letters along with their place in the alphabet. You get this for free with the standard Enumerable methods like each_with_index in 1.9, so you can just write ('a'..'z').each_with_index to get the enumerator.
I've seen Fibers used in that way, look at an example from this article:
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
20.times { puts fib.resume }
If you are looking to lazily generate values, #Chuck's answer is the correct one.
If you are looking to lazily iterate over a collection, Ruby 2.0 introduced the new .lazy enumerator.
range = 1..Float::INFINITY
puts range.map { |x| x+1 }.first(10) # infinite loop
puts range.lazy.map { |x| x+1 }.first(10) # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Ruby supports generators out of the box using Enumerable::Generator:
require 'generator'
# Generator from an Enumerable object
g = Generator.new(['A', 'B', 'C', 'Z'])
while g.next?
puts g.next
end
# Generator from a block
g = Generator.new { |g|
for i in 'A'..'C'
g.yield i
end
g.yield 'Z'
}
# The same result as above
while g.next?
puts g.next
end
https://ruby-doc.org/stdlib-1.8.7/libdoc/generator/rdoc/Generator.html
Class Enumerator and its method next behave similar
https://docs.ruby-lang.org/en/3.1/Enumerator.html#method-i-next
range = 1..Float::INFINITY
enumerator = range.each
puts enumerator.class # => Enumerator
puts enumerator.next # => 1
puts enumerator.next # => 2
puts enumerator.next # => 3

Resources