Mike
Mike

Reputation: 882

How to make infinite each_with_object actually lazy?

tl;dr

I expect both of those lines to return {:a => 10}

p (0..5).each_with_object({a: 0}) { |i, acc| acc[:a] += i}
p (0..Float::INFINITY).lazy.each_with_object({a: 0}) { |i, acc| acc[:a] += i}.first(5)

However, the second line loops forever.

Longer version

This example is of course simplified whilst the real problem is very similar. The idea is to run each_with_object body until certain condition is met, however this condition relies on result that this very each_with_object function generates over each iteration so the stop condition must come after each_with_object.

The closest I could get was

p (0..Float::INFINITY).lazy.each_with_object({a: 0}).take_while{ |i, acc| acc[:a] += i}.first(5)

but the result is not exactly what I want as it returns the following

[[0, {:a=>10}], [1, {:a=>10}], [2, {:a=>10}], [3, {:a=>10}], [4, {:a=>10}]]

which not only forces me to do .last.last but also makes the array grow immensely as I iterate many times (and also it stores 5 times the {:a => 10} instead of {a:=>0}, {a:=>1}, {a:=>3}, ... which is also a mystery to me.

Upvotes: 0

Views: 206

Answers (1)

Stefan
Stefan

Reputation: 114248

each_with_object only returns an enumerator if you don't pass a block.

Maybe you could create your own method:

class Enumerator::Lazy
  def my_each_with_object(obj)
    Lazy.new(self) do |yielder, *args|
      yield *args, obj
      yielder << obj
    end
  end
end

(1..Float::INFINITY).lazy
                    .my_each_with_object({a: 0}) { |i, acc| acc[:a] += i }
                    .drop_while { |acc| acc[:a] <= 10 }
                    .first
#=> {:a=>15}

I've used acc[:a] <= 10 as a condition because you said that it depends on the value calculated by each_with_object.

Upvotes: 2

Related Questions