在Ruby 1.9中对无限枚举器进行惰性求值 - 在不同类的对象上调用实例方法

时间:2013-08-08 13:47:44

标签: ruby lazy-evaluation enumerator

我正在尝试使用Ruby 1.9来处理一个枚举器的Lazy Evaluation。这是正在进行的工作,所以可能会有其他错误/缺少代码,但我现在有一个特定的问题。我正试图通过这个测试(注意我不能改变测试):

def test_enumerating_with_a_single_enumerator
  enumerator = SomeClass.new(some_infinite_sequence.to_enum)
  assert_equal [1, 2, 3, 4, 5], enumerator.take(5)
end

我在下面编写了这段代码,我知道问题是我从 initialize 方法的参数中调用SomeClass中的 lazy_select 实例方法是Enumerator类的一个实例,所以我得到一个NoMethodError。有什么建议?谢谢。

class SomeClass < Enumerator

  def initialize(*enumerators)
    super() do |yielder|
      enumerators.each do |enumerator|
        enumerator.lazy_select { |yielder, first_value, second_value| yielder.yield first_value if (first_value <=> second_value) <= 0 }
        .first(20)
      end
    end
  end

  def lazy_select(&block)
    self.class.new do |yielder|
      each_cons(2) do |first_value, second_value|
        block.call(yielder, first_value, second_value)
      end
    end
  end
end

2 个答案:

答案 0 :(得分:0)

  

我现在有一个具体问题。我正试图通过这个测试   (注意我不能改变测试):

def test_enumerating_with_a_single_enumerator
  enumerator = SomeClass.new(some_infinite_sequence.to_enum)
  assert_equal [1, 2, 3, 4, 5], enumerator.take(5)
end
class SomeClass < Enumerator

  def initialize(enum, &block)
    super() do |y|
      begin
        enum.each do |val|
          if block
            block.call(y, val)  #while initializing sc2 in Line B execution takes this branch
          else
            y << val  #while initializing sc1 from Line A execution halts here
          end         
        end
      rescue StopIteration
      end

    end
  end

  def lazy_take(n)
    taken = 0

    SomeClass.new(self) do |y, val|  #Line B
      if taken < n
        y << val
        taken += 1
      else
        raise StopIteration
      end
    end
  end

  def take(n)
    lazy_take(n).to_a
  end
end

sc1 = SomeClass.new( (1..6).cycle )   #Line A
p sc1.take(10)

--output:--
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4]

sc2是我给lazy_take()内部创建的匿名实例的名称。

代码很难理解。代码设置为sc1的枚举器为循环,sc2的枚举器为sc1(initialize()要求第一个arg为枚举器)。初始化sc1时,代码开始逐步执​​行循环中的值并在此行停止:

y << val

然后,当调用lazy_take()时,会创建sc2,并且其初始化代码开始单步执行sc1中的值。但是sc1中没有值,因此sc1执行以下行:

y << val

将循环中的值注入sc1的yielder。然后sc1的yielder立即将val输出到sc2 - 因为在sc2的代码中,each()方法要求sc1中的值。然后sc2接受val并将其注入sc2的yielder。然后,sc2中每个块的下一次迭代发生,并且sc2的代码再次要求来自sc1的值。 sc2反复要求sc1中的值,这会导致sc1传递从循环中检索到的值。一旦sc2运行循环n次,它就会停止从sc1请求值。下一步是让sc2放弃它的yielder中的值。

如果您愿意,可以像这样定义initialize():

def initialize(enum)
    super() do |y|
      begin
        enum.each do |val|
          if block_given?
            yield y, val  #while initializing sc2 in Line B execution takes this branch
          else
            y << val  #while initializing sc1 from Line A execution halts here
          end         
        end
      rescue StopIteration
      end

    end
  end

这表明您不必指定块参数并显式调用()块。相反,您可以省去block参数并调用yield(),这些值将自动发送到块。

答案 1 :(得分:0)

感谢上面收到的评论。他们非常有帮助。我设法按如下方式解决了这个问题:

class SomeClass < Enumerator

  class SomeOtherClass < RuntimeError

    attr_reader :enumerator

    def initialize(enumerator)
      @enumerator = enumerator
    end
  end


  def initialize(*enumerators)
    super() do |yielder|
      values = []
      enumerators.each do |enumerator|
        values.push lazy_select(enumerator) { |value| sorted? enumerator }.take(@number_to_take)
      end
      values.flatten.sort.each { |value| yielder.yield value }
    end
  end

  def lazy_select(enumerator, &block)
    Enumerator.new do |yielder|
      enumerator.each do |value|
        yielder.yield value if block.call enumerator
      end
    end
  end

  def sorted?(enumerator)
    sorted = enumerator.each_cons(2).take(@number_to_take).all? { |value_pair| compare value_pair }
    sorted || raise(SomeClass::SomeOtherClass, enumerator)
  end

  def compare(pair)
    pair.first <= pair.last
  end

  def take(n)
    @number_to_take = n
    super
  end
end

这通过了我的所有测试。