Skip to content

Instantly share code, notes, and snippets.

@geeksilva97
Last active July 21, 2024 16:57
Show Gist options
  • Save geeksilva97/95266a1382cf68aaf5407138aceff154 to your computer and use it in GitHub Desktop.
Save geeksilva97/95266a1382cf68aaf5407138aceff154 to your computer and use it in GitHub Desktop.
Having fun implementing my own Enumerator in plain Ruby. From scratch.
fib = Traversor.new do |yielder|
a = b = 1
loop do
yielder << a
a, b = b, a + b
end
end
simple_iterator = Traversor.new do |y|
y << 1
y << 2
y << 3
y << 4
y << 5
y << 6
end
pp simple_iterator.next
pp simple_iterator.next
pp simple_iterator.next
puts "=== Fibonacci ==="
puts fib.next
puts fib.next
puts fib.next
puts fib.take(10).inspect
puts "=== Lazy ==="
l = simple_iterator.lazy
.filter(&:odd?)
.take(1)
puts l
puts l.to_a.inspect
lazy_fib = fib.lazy
puts lazy_fib.take(5).to_a.inspect
require 'fiber'
class Traversor
def initialize(&block)
@block = block
end
def rewind
start_fiber
end
def lazy
Traversor::Lazy.new(&@block)
end
def each(&each_block)
return self unless block_given?
yielder = Traversor::Yielder.new(&each_block)
@block.call(yielder)
end
def next
start_fiber unless @fiber
value = @fiber.resume
raise StopIteration unless value && @fiber.alive?
value
end
def map(&map_block)
result = []
each do |item|
result << map_block.call(item)
end
end
def filter(&filter_block)
result = []
each do |item|
result << item if filter_block.call(item)
end
end
def take(n)
result = []
# fica um pingue-pongue entre o Traversor atraves do each e o Traversor::Yielder
each do |item|
result << item
break if result.size == n
end
result
end
private
def start_fiber
@fiber = Fiber.new do
yielder = Yielder.new
@block.call(yielder)
end
end
def fiber_yielder
@fiber_yielder ||= Fiber.new do
@block.call(self)
end
end
end
class Traversor::Yielder
def initialize(&yielder_block)
@yielder_block = yielder_block
end
def yield(item)
return @yielder_block.call(item) if @yielder_block
Fiber.yield(item)
end
alias << yield
def next
Fiber.yield(10)
end
end
class Traversor::Lazy
def initialize(&block)
@block = block
end
def each(&each_block)
traversor = Traversor.new(&@block)
traversor.each(&each_block)
end
def map(&map_block)
Traversor::Lazy.new do |yielder|
each do |item|
yielder << map_block.call(item)
end
end
end
def filter(&filter_block)
Traversor::Lazy.new do |yielder|
each do |item|
yieldable = filter_block.call(item)
puts "filter was called for item #{item} :: yieldable #{yieldable}"
yielder << item if yieldable
end
end
end
def take(size)
raise ArgumentError.new('attempt to take a negative size') if size.negative?
Traversor::Lazy.new do |yielder|
count = 0
each do |item|
puts "take block executed :: count #{count} | size #{size}"
yielder << item if (size - count).positive?
count += 1
# checks again to be efficient, not breaking here cause the whole pipeline to execute again till this step again.
break if count >= size
end
end
end
def lazy
self
end
def to_a
results = []
each do |item|
results << item
end
results
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment