How can I cache param-based Enumerator call results?

I’m trying to cache a lazy-evaluated, param-based Enumerator calls.

Considering the following code:

def arith a, b
Enumerator.new do |yielder|
[‘+’, ‘*’].each do |oper|
sleep 1 # simulates a param-based, long-running process
yielder.yield [oper, eval(“#{a} #{oper} #{b}”)]
end
end
end

[[1,2], [3,4], [5,6], [1,2], [3,4], [5,6], [7,8]].each do |a, b|
arith(a,b).each do |oper, result|
puts “#{a} #{oper} #{b} = #{result}”
end
end

should take about 14 seconds to run, with arith() lazily returning the
sum and product of its params. Still, if you look closely, arith(1,2),
arith(3,4) and arith(5,6) are called twice; if there was caching of
the yielded values there would be no need to recompute these sums
and products (and the whole process would take around eight seconds
instead).

I’m still rather new to Enumerators, but in my project their lazy
evaluation is an amazing benefit. What’s the best/simplest way to
introduce caching in the above scenario?

(Ideally, I’d love to have the caching done outside the Enumerator,
as in my project’s case the arith() method is provided by various
interchangeable modules¹, but if it’s only – or much easier – doable
from inside the Enumerator then so be it, I’ll add it in every
implementation…)

¹

– ignoring that #decompositions is itself an Enumerator, what I want is
for qv_gen to not recompute blankets if qv_gen.blankets(fsm, u, v, qu)
was already called previously with the same params

Thanks in advance for any insight in how to solve this problem!

— Shot

Shot (Piotr S.):

I’m trying to cache a lazy-evaluated, param-based Enumerator calls.

Considering the following code:

def arith a, b
Enumerator.new do |yielder|
[’+’, ‘*’].each do |oper|
sleep 1 # simulates a param-based, long-running process
yielder.yield [oper, eval("#{a} #{oper} #{b}")]
end
end
end

[[1,2], [3,4], [5,6], [1,2], [3,4], [5,6], [7,8]].each do |a, b|
arith(a,b).each do |oper, result|
puts “#{a} #{oper} #{b} = #{result}”
end
end

should take about 14 seconds to run, with arith() lazily returning the
sum and product of its params. Still, if you look closely, arith(1,2),
arith(3,4) and arith(5,6) are called twice; if there was caching of
the yielded values there would be no need to recompute these sums
and products (and the whole process would take around eight seconds
instead).

I ended up doing the below, which nicely caches the stuff outside the
Enumerator. Any improvements (I have a feeling that the each…puts…end
blocks can be DRY-ed up by factoring them out) most welcome, of course!

def arith a, b
Enumerator.new do |yielder|
[’+’, ‘*’].each do |oper|
sleep 1 # simulates a param-based, long-running process
yielder.yield [oper, eval("#{a} #{oper} #{b}")]
end
end
end

cache = {}
[[1,2], [3,4], [5,6], [1,2], [3,4], [5,6], [7,8]].each do |a, b|
if cache[[a,b]]
cache[[a,b]].each do |oper, result|
puts “#{a} #{oper} #{b} = #{result}”
end
else
cache[[a,b]] = []
arith(a,b).each do |oper, result|
puts “#{a} #{oper} #{b} = #{result}”
cache[[a,b]] << [oper, result]
end
end
end

— Shot