【问题标题】:Creating lists using yield in Ruby and Python在 Ruby 和 Python 中使用 yield 创建列表
【发布时间】:2010-10-11 04:07:38
【问题描述】:

我正在尝试想出一种优雅的方法来从一个在 Python 和 Ruby 中产生值的函数创建一个列表。

在 Python 中:

def foo(x):
    for i in range(x):
        if bar(i): yield i 
result = list(foo(100))

在 Ruby 中:

def foo(x)
  x.times {|i| yield i if bar(i)}
end
result = []
foo(100) {|x| result << x}

虽然我喜欢同时使用这两种语言,但我一直对 Ruby 版本必须初始化列表然后填充它感到有些困扰。 Python 的yield 导致简单的迭代,这很棒。 Ruby 的yield 调用了一个块,这也很棒,但是当我只想填写一个列表时,感觉有点笨拙。

还有更优雅的 Ruby 方式吗?

UPDATE 重新设计了示例,以表明函数产生的值的数量不一定等于 x。

【问题讨论】:

  • Ruby 和 Python 的“yield”在语义上是不同的。 Python 的产量比 ruby​​ 的产量几乎更接近 ruby​​ 的 Fiber.yield。

标签: python ruby list yield


【解决方案1】:

我知道这不是您想要的,但是用 ruby​​ 表达您的示例的更优雅的方式是:

result = Array.new(100) {|x| x*x}

【讨论】:

  • 你说得对,这不是我想要的。我会从一个方法中产生的唯一原因是如果我不知道究竟有多少值会被产生。
【解决方案2】:
def squares(x)
  (0..x).map { |i| i * i }
end

任何涉及值范围的事情最好用范围来处理,而不是times 和数组生成。

【讨论】:

  • 这个例子是人为的。这个问题与任何产生值的函数有关,无论是否涉及范围。
【解决方案3】:

因此,对于您的新示例,请尝试以下操作:

def foo(x)
  (0..x).select { |i| bar(i) }
end

基本上,除非您正在编写自己的迭代器,否则在 Ruby 中您不需要经常使用 yield。如果您停止尝试使用 Ruby 语法编写 Python 习语,您可能会做得更好。

【讨论】:

  • 同意。在很多情况下,两种语言对等价问题都有完全不同的方法。
  • 你甚至不需要 to_a — Range 包括 Enumerable。只需 (1..x).select {|i|栏 i}。
  • 我什至不会称其为 Python 习语,请参阅下面的 Python 建议。
【解决方案4】:

对于 Python 版本,我会使用如下生成器表达式:

(i for i in range(x) if bar(i))

或者对于这种过滤值的特定情况,更简单

itertools.ifilter(bar,range(x))

【讨论】:

    【解决方案5】:

    对于 stbuton 发布的 Python 列表理解版本,如果您需要生成器,请使用 xrange 而不是 range。 range 将在内存中创建整个列表。

    【讨论】:

      【解决方案6】:

      yield 表示 ruby​​ 和 python 不同的东西。在 ruby​​ 中,如果我没记错的话,你必须指定一个回调块,而 python 中的生成器可以传递给任何持有它们的人。

      【讨论】:

      • python 中的 IMO 生成器一团糟。它们“看起来”像函数,但与它们完全不同 --- 您必须在函数中搜索“产量”,然后才能将它们识别为生成器而不是函数。既然生成器“函数”无论如何都会返回生成器对象……为什么不明确地让 API 像在 Ruby 中那样构建生成器对象呢?在这里,Ruby 似乎比 Python 更明确——python 试图将它们伪装成函数(这完全是错误的抽象)。
      • @banister:我对这种差异有不同的反应。据我对 ruby​​ 的了解,(不是很远)可以通过简单地接受函数引用参数并使用每个“生成”值连续调用它来完成与 ruby​​ 生成器的等效构造。这只需要 Python 也有的一流函数。 python 方法使用延续,它提供了更深层次的东西。 yield 周围的神奇语法对开发人员来说不应该太重要,因为它也可以从常规函数返回生成器对象,比如生成器推导。
      • @recursive,我不太明白python中的等效结构是如何工作的,你能举个例子吗?对于记录 Ruby 生成器是使用纤程实现的,纤程是完全成熟的语言支持的协同程序。此外,红宝石有一流的延续:P
      • @banister:我可能错了。我上次查看 ruby​​ 是在 1.9.1 之前,即使那时我也不确定,但我对 ruby​​ 生成器的理解是需要接受函数引用或类似的东西。每个生成的值都会一次传递给这个引用的函数。相比之下,在 python 中,默认情况下生成器更像惰性迭代器。我不知道红宝石有延续。我绝对有可能对 ruby​​ 不公平。
      • @recursive,啊...是的,您说的是 ruby​​ 的内部迭代器:array.each { |i| puts i}。他们采取一个块(一个匿名函数)并按照您的说法工作。但是我说的是Enumerators,它们是外部迭代器;它们是使用 Fibers 实现的,并且也像惰性迭代器一样工作。见这里:gist.github.com/596582 这两种方法看起来是等价的,不同之处在于 ruby​​ 通过使用 Enumerator.new 显式创建生成器对象,而 python 使用看起来像函数定义的东西隐式创建。
      【解决方案7】:

      与您的 Python 代码(使用 Ruby 生成器)完全等效的是:

      def foo(x)
          Enumerator.new do |yielder|
              (0..x).each { |v| yielder.yield(v) if bar(v) }
          end
      end
      
      result = Array(foo(100))
      

      在上面,列表是延迟生成的(就像在 Python 示例中一样);见:

      def bar(v); v % 2 == 0; end
      
      f = foo(100)
      f.next #=> 0
      f.next #=> 2
      

      【讨论】:

        猜你喜欢
        • 2020-04-16
        • 1970-01-01
        • 1970-01-01
        • 2017-12-28
        • 2011-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-18
        相关资源
        最近更新 更多