Reputation: 193
I'm writing a method to merge two streams of numbers, and I've two alternative implementations:
def merge1(l1, l2)
Enumerator.new do |yielder|
h = case l1.peek <=> l2.peek
when -1 then l1.next
when +1 then l2.next
else l1.next; l2.next
end
yielder << h
yielder << merge1(l1, l2).next
end.lazy
end
def merge2(l1, l2)
Enumerator.new do |yielder|
loop do
h = case l1.peek <=> l2.peek
when -1 then l1.next
when +1 then l2.next
else l1.next; l2.next
end
yielder << h
end
end.lazy
end
puts merge2((1..Float::INFINITY).lazy.map {|x| x * 2}, (1..Float::INFINITY).lazy.map {|x| x * 3}).first(10)
But merge1
only prints "2 3" while merge2
produces the correct result.
Upvotes: 4
Views: 485
Reputation: 520
Rewriting the method to use yield
might improve comprehension; you can then use Object#enum_for, thereby separating concerns:
While I would discourage recursion in this case (see alternative loop
approach below), here's how that would work:
def merge1(l1, l2, &block)
h = case l1.peek <=> l2.peek
when -1 then l1.next
when +1 then l2.next
else l1.next
l2.next
end
yield h
merge1(l1, l2, &block)
end
enum_for(:merge1,
(1..Float::INFINITY).lazy.map { |x| x * 2 },
(1..Float::INFINITY).lazy.map { |x| x * 3 }
).lazy.first(10)
In this case, using a straight-forward loop would be a better approach than recursion because loop
automatically handles StopIteration
exceptions in case one passes non-infinite lists, and because you can easily use Enumerator::new...a good reminder to use the right tool for the job:
def merge(l1, l2)
Enumerator.new do |yielder|
loop do
h = case l1.peek <=> l2.peek
when -1 then l1.next
when +1 then l2.next
else l1.next
l2.next
end
yielder << h
end
end
end
merge((1..5).lazy.map { |x| x * 2 },
(1..5).lazy.map { |x| x * 3 }
).lazy.first(10).to_a
# => [2, 3, 4, 6, 8, 9, 10]
# No need to handle `StopIteration` exceptions;
# still supports infinite ranges.
Upvotes: 0
Reputation: 368944
You need to yield each item that sub-enumerator generate:
def merge1(l1, l2)
Enumerator.new do |yielder|
h = case l1.peek <=> l2.peek
when -1 then l1.next
when +1 then l2.next
else l1.next; l2.next
end
yielder << h
merge1(l1, l2).each do |h| # <----
yielder << h # <----
end # <----
end.lazy
end
Upvotes: 4