Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Ruby中作为无限生成器的枚举数_Ruby - Fatal编程技术网

在Ruby中作为无限生成器的枚举数

在Ruby中作为无限生成器的枚举数,ruby,Ruby,我正在阅读一个资源,解释如何将枚举数用作生成器,例如: triangular_numbers = Enumerator.new do |yielder| number = 0 count = 1 loop do number += count count += 1 yielder.yield number end end print triangular_numbers.next, " " print triangular_numbers.next,

我正在阅读一个资源,解释如何将枚举数用作生成器,例如:

triangular_numbers = Enumerator.new do |yielder|
  number = 0
  count = 1
  loop do
    number += count
    count += 1
    yielder.yield number
  end
end

print triangular_numbers.next, " " 
print triangular_numbers.next, " " 
print triangular_numbers.next, " " 
我不理解这里的
yielder
的目的,它需要什么值,以及该代码如何与程序的其余代码并行执行

执行从顶部开始,可能在块“产生”代码值时暂停


有人能解释一下这些在编译器眼中是如何执行的吗?

我想我发现了一些你可能会感兴趣的东西

本文:解释了急切和懒惰评估背后的思想,并解释了它与Enumerale、Generator或Yielder等“框架类”的关系。它主要集中在解释如何实现懒散评估,但仍然非常详细


原始资料来源: Ruby 2.0使用一个名为Enumerator::lazy的对象实现惰性计算。它的特殊之处在于它同时扮演两个角色!它是一个枚举器,还包含一系列可枚举方法。它调用每个函数以从枚举源获取数据,并向枚举的其余部分生成数据。 由于Enumerator::Lazy同时扮演这两个角色,因此可以将它们链接在一起以生成单个枚举。

这是Ruby中延迟计算的关键。数据源中的每个值都会产生到我的块中,然后结果会立即沿着枚举链传递。此枚举不急切–枚举器::Lazy#collect方法不会将值收集到数组中。相反,每一个值都是通过重复的产量沿着Enumerator::Lazy对象链一次传递一个值。如果我将一系列用于collect或其他Enumerator::Lazy方法的调用链接在一起,则每个值都会沿着链从一个块传递到下一个块,每次传递一个

Enumerable#首先两者都通过调用惰性枚举数来启动迭代,并在具有足够值时通过引发异常来结束迭代

在一天结束时,这是惰性评估背后的关键思想:计算链末端的函数或方法开始执行过程,程序流通过函数调用链反向工作,直到它获得所需的数据输入。Ruby使用枚举器::惰性对象链来实现这一点


Yielder只是一段返回值并等待下次调用的代码

这可以通过使用轻松实现。请参见以下创建
simplenumerator
类的示例:

class SimpleEnumerator

  def initialize &block
    # creates a new Fiber to be used as an Yielder
    @yielder  = Fiber.new do
      yield Fiber # call the block code. The same as: block.call Fiber
      raise StopIteration # raise an error if there is no more calls
    end
  end

  def next
    # return the value and wait until the next call
    @yielder.resume
  end

end

triangular_numbers = SimpleEnumerator.new do |yielder|
  number  = 0
  count   = 1
  loop do
    number  += count
    count   += 1
    yielder.yield number
  end
end

print triangular_numbers.next, " " 
print triangular_numbers.next, " " 
print triangular_numbers.next, " " 
我刚刚用simplenumerator.new替换了代码中的
Enumerator.new
,结果是一样的


存在“轻量级协作并发”;使用Ruby文档的话,程序员可以安排应该做什么,换句话说,程序员可以暂停并恢复代码块。

假设我们要打印前三个三角形数字。一个简单的实现是使用一个函数:

def print_triangular_numbers steps
  number = 0
  count = 1
  steps.times do
    number += count
    count += 1
    print number, " "
  end
end

print_triangular_numbers(3)
这里的缺点是我们混合了打印逻辑和计数逻辑。如果我们不想打印数字,这是没有用的。我们可以通过将数字生成一个块来改进这一点:

def triangular_numbers steps
  number = 0
  count = 1
  steps.times do
    number += count
    count += 1
    yield number
  end
end

triangular_numbers(3) { |n| print n, " " }
现在假设我们想要打印一些三角形数字,做一些其他的事情,然后继续打印它们。同样,一个简单的解决方案:

def triangular_numbers steps, start = 0
  number = 0
  count = 1
  (steps + start).times do
    number += count
    yield number if count > start
    count += 1
  end
end

triangular_numbers(4) { |n| print n, " " }

# do other stuff

triangular_numbers(3, 4) { |n| print n, " " }
number = 0
count = 1
triangular_numbers = proc do |&blk|
  number += count
  count += 1
  blk.call number
end

4.times { triangular_numbers.call { |n| print n, " " } }

# do other stuff

3.times { triangular_numbers.call { |n| print n, " " } }
这样做的缺点是,每次我们想要恢复打印三角形数字时,都需要从头开始。效率低下!我们需要的是一种方法来记住我们在哪里中断了,以便我们以后可以继续。带有proc的变量可以轻松解决问题:

def triangular_numbers steps, start = 0
  number = 0
  count = 1
  (steps + start).times do
    number += count
    yield number if count > start
    count += 1
  end
end

triangular_numbers(4) { |n| print n, " " }

# do other stuff

triangular_numbers(3, 4) { |n| print n, " " }
number = 0
count = 1
triangular_numbers = proc do |&blk|
  number += count
  count += 1
  blk.call number
end

4.times { triangular_numbers.call { |n| print n, " " } }

# do other stuff

3.times { triangular_numbers.call { |n| print n, " " } }
但这是前进一步,后退两步。我们可以很容易地恢复,但没有封装逻辑(我们可能会意外地更改
编号
并毁掉一切!)。我们真正想要的是一个对象,我们可以在其中存储状态。这正是枚举器的作用

triangular_numbers = Enumerator.new do |yielder|
  number = 0
  count = 1
  loop do
    number += count
    count += 1
    yielder.yield number
  end
end

4.times { print triangular_numbers.next, " " }

# do other stuff

3.times { print triangular_numbers.next, " " }
由于块在Ruby中是闭包,
循环
会记住调用之间的
number
count
状态。这就是为什么看起来枚举器是并行运行的


现在我们来谈谈元老。请注意,它替换了前面示例中使用proc的
blk.call number
<代码>blk.call
起作用,但它不灵活。在Ruby中,您不必总是为枚举数提供块。有时,您只想一次枚举一个步骤或将枚举数链接在一起,在这种情况下,让枚举数简单地将值传递给块是不方便的
Enumerator
通过提供不可知的
Enumerator::Yielder
接口,使枚举数的编写更加简单。当您给yielder(
yielder.yield number
yielder一个值时,我在Ruby Cookbook中找到了一个简洁的答案:

这展示了如何使用Ruby 2.0+
枚举器类创建Ruby 1.8样式的
生成器

my_array = ['v1', 'v2']

my_generator = Enumerator.new do |yielder|
    index = 0
    loop do
        yielder.yield(my_array[index])
        index += 1
    end
end

my_generator.next    # => 'v1'
my_generator.next    # => 'v2'
my_generator.next    # => nil

+1,好问题。每个人都知道,当给定一个块时,枚举数可以一个接一个地向该块生成枚举。基本调查会告诉你,新构造的枚举数为自己创建了一个
enumerator::Yielder
的实例。然而,
enumerator::Yielder
的文档很难说是什么出于种种原因,设计师们将屈服的任务从
枚举器
委派给
枚举器::Yielder
。我期待着答案。@daremarkovic:IIRC,实际上,它不是并行执行的。下面的机制称为“协同路由”,它是“公正的”回调/委托的巧妙编排。@BorisStitnicky:请看我找到的文章。我发现它很有趣,我认为关于“yielder”的部分解释了你的疑惑。@Quetzalcatl:这是一篇很棒的文章。找到工作很好