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
Related
I need help understanding inheritance.
class MyArray < Array
end
a = MyArray[1, 2, 3] #=> [1, 2, 3]
b = MyArray[4, 5] #=> [4, 5]
c = a + b #=> [1, 2, 3, 4, 5]
a.class #=> MyArray
b.class #=> MyArray
c.class #=> Array
I don't understand why the result of the addition is not an instance of MyArray class.
I don't understand why my array "a" is not "MyArray" class after add.
Why should it (be a MyArray)? Concatenation operation on arrays is defined to return a new Array, so that's what happens here. https://ruby-doc.org/core-2.5.3/Array.html#method-i-2B
If you want, you can override that operation in your class to return an instance of MyArray. Don't forget about all other similar methods.
This is also why it's a bad idea to subclass standard collections. Better to use composition over inheritance here.
Just to add a bit to Sergio's answer in terms of his comment on using composition over inheritance and the exchange in the comments.
Instead of saying MyArray is an array you can say MyArrayLike has and array.
Then you can "forward" methods that make sense to the underlying array but still add your own functionality that makes sense for your class without sub-classing array.
Ruby even has several ways to make this very easy including the Forwardable module.
class MyArrayLike
attr_reader :arr
def initialize( initial_arr )
#arr = initial_arr
end
def +(other)
result = self.class.new(arr + other.arr)
# maybe you want to do more than just concat the underlying array, if so you can do it here
result
end
def first
# for example maybe you want first to just return the first item in the underlying array.
arr.first
end
end
a = MyArrayLike.new([1,2,3])
b = MyArrayLike.new([4,5])
puts "a.class = #{a.class}"
# => a.class = MyArrayLike
puts a
# => #<MyArrayLike:0x00000000dc4b00>
a += b
puts "a.class = #{a.class}"
# => a.class = MyArrayLike
puts a
# => #<MyArrayLike:0x00000000dc4470>
puts a.first
# => 1
puts a.arr
# => 1
# 2
# 3
# 4
# 5
Adding MyArray to MyArray to get Array may be counterintuitive, but a method can be defined to return any class. And in the case of Array#+, which you are calling, it just happens to be defined to return an Array. That's it.
If you want it to return a MyArray, one way to do it is to define MyArray#+ as follows:
class MyArray < Array
def +other
MyArray.new(super)
end
end
(MyArray.new([1, 2, 3]) + MyArray.new([4, 5])).class # => MyArray
By the way, notice that your MyArray#initialize definition is meaningless, and hence redundant.
How do I return Enumerator from my array wrapper without using already existing array iterators?
class MyArray
def initialize
#inner = []
end
def each
index = 0
while index < #inner.size
yield #inner[index] if block_given?
index += 1
end
end
end
I can't figure out how to avoid calling things like #inner.each at the end of the each method.
Given:
#inner = [1, 2, 3]
Code
#inner.to_enum
will return an enumerator.
enum = #inner.to_enum
enum.each{|e| p e}
# => 1, 2, 3
How do I call a method, given its name, on an element of an array?
For example, I could have:
thing = "each"
I want to be able to do something like:
def do_thing(thing)
array = [object1,object2]
array[0].thing
end
so that do_thing(to_s), for example, would run object1.to_s.
You can use public_send or send. public_send only sends to public methods while send can see public and private methods.
def do_thing(thing)
array = [1,2,3]
array.public_send(thing)
end
do_thing('first')
# => 1
do_thing(:last)
# => 3
Update A more general version:
def do_thing(array, index, method, *args)
array[index].public_send(method, *args)
end
do_thing([1, 2, 3], 0, :to_s)
# => "1"
do_thing([[1,2], [3, 4]], 0, :fetch, 0)
# => 1
require 'ostruct'
o = OpenStruct.new(attribute: 'foo')
do_thing([o], 0, :attribute=, 'bar')
o.attribute == 'bar'
# => true
Object#send
thing = "each"
def do_thing(thing)
array = [1,2,3]
array.send(thing)
end
From the doc:
class Klass
def hello(*args)
"Hello " + args.join(' ')
end
end
k = Klass.new
k.send :hello, "gentle", "readers" #=> "Hello gentle readers"
Here is an example to help you out although I don't have any idea what objects are residing inside your array:
arr = [Array.new(2,10),"abc" ]
arr.each{|i| p i.send(:length)}
#>>2
#>>3
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.
If I want a part of an array I can use [] or split:
arr = [1,2,3,4,5]
arr[1..3]
=> [2, 3, 4]
But is there a 'general' version of []? Can I apply it to any Enumerator?
enum = arr.each
enum.xxx_method(1..3) # is equal to arr[1..3].each
Of course you can use arr[1..3] directly. But I'm seeking a general way to handle any enumerator.
If you have an enumerator, you can count on Enumerable methods drop and take:
# abstract if necessary as enum_slice(range)
enumerator.drop(2).take(3)
If that enumerator is an array you don't need to traverse it, check the method Array#lazy_slice that I asked to be added to enumerable_lazy in relation with your previous question:
require 'enumerable/lazy'
class Array
def lazy_slice(range)
Enumerator.new do |yielder|
range.each do |index|
yielder << self[index]
end
end.lazy
end
end
some_big_array = (0..10000).to_a # fake array, it won't generally be a range
p some_big_array.lazy_slice(9995..10000).map { |x| 2*x }.to_a
#=> [19990, 19992, 19994, 19996, 19998, 20000]