Ruby有像Python列表推导式一样的东西吗?

10

Python有一个很好的特性:

print([j**2 for j in [2, 3, 4, 5]]) # => [4, 9, 16, 25]

Ruby 中这甚至更简单:

puts [2, 3, 4, 5].map{|j| j**2}

但如果涉及到嵌套循环的话,Python 似乎更加方便。

在 Python 中我们可以这样做:

digits = [1, 2, 3]
chars = ['a', 'b', 'c']    
print([str(d)+ch for d in digits for ch in chars if d >= 2 if ch == 'a'])    
# => ['2a', '3a']

在 Ruby 中的等价代码如下:

digits = [1, 2, 3]
chars = ['a', 'b', 'c']
list = []
digits.each do |d|
    chars.each do |ch|
        list.push d.to_s << ch if d >= 2 && ch == 'a'
    end
end
puts list

Ruby 有类似的东西吗?


3
这里讨论了类似的内容:https://dev59.com/0XVC5IYBdhLWcg3wZwFT。 - Bharat
2
简而言之,Ruby有一些类似的东西,但没有完全相同的,也没有像它那样简洁(或易读)。长话短说...就是RBK链接的答案。 - abarnert
1
@RBK 是的,但这不是关于嵌套循环(1+数组)的问题。 - defhlt
有些回答可以适用于嵌套循环,而有些则不行。 - abarnert
3个回答

12

在 Ruby 中,通常的方法是将 EnumerableArray 方法正确地组合起来,以达到相同的效果:

digits.product(chars).select{ |d, ch| d >= 2 && ch == 'a' }.map(&:join)

这比列表推导式仅多了4个字符左右,表达力也一样强(当然这是我的看法,但由于列表推导式只是列表Monad的一个特殊应用,因此可以认为使用Ruby的集合方法可以足够重建它),而且不需要任何特殊的语法。


2

正如您所知,Ruby没有列表推导的语法糖,因此您可以通过创造性地使用块来实现更接近的效果。人们提出了不同的想法,请查看lazylistverstehen方法,两者都支持带条件的嵌套推导:

require 'lazylist'
list { [x, y] }.where(:x => [1, 2], :y => [3, 4]) { x+y>4 }.to_a
#=> [[1, 4], [2, 3], [2, 4]]

require 'verstehen'
list { [x, y] }.for(:x).in { [1, 2] }.for(:y).in { [3, 4] }.if { x+y>4 }.comprehend
#=> [[1, 4], [2, 3], [2, 4]]

当然,这并不是你所谓的惯用 Ruby 写法,因此通常更安全的做法是使用典型的 product + select + map 方法。


1
正如RBK所建议的那样,Ruby中的列表推导式提供了许多不同的方法来做类似于Ruby中的列表推导式的事情。
虽然它们中没有一个明确地描述嵌套循环,但至少其中一些可以很容易地嵌套。
例如,Robert Gamble的被接受的答案建议添加一个Array#comprehend方法。
class Array
  def comprehend(&block)
    return self if block.nil?
    self.collect(&block).compact
  end
end

完成上述步骤后,您可以编写以下代码:

digits.comprehend{|d| chars.comprehend{|ch| d.to_s+ch if ch =='a'} if d>=2}

与 Python 代码相比:

[str(d)+ch for d in digits for ch in chars if d >= 2 if ch == 'a']

这些差异相当微小:

  1. Ruby 代码有点长。但这主要是因为 "comprehend" 被拼出来了;如果你想的话,总可以给它取个更短的名字。
  2. Ruby 代码按不同的顺序排列——数组放在开头而不是中间。但如果你仔细想想,这正是你期望和想要的,因为它符合“一切皆方法”的哲学。
  3. Ruby 代码需要嵌套大括号来实现嵌套推导。我想不到任何明显的方法来避免这种情况,否则会让事情变得更糟(你不想调用 "[str,digits].comprehend2" 或其他类似的东西...)。

当然,Python 的真正优势在于,如果你决定惰性地评估列表,你只需删除括号(或根据上下文将它们转换为括号),就可以将推导式转换为生成器表达式。但即使在那里,你也可以创建一个 Array#lazycomprehend 或类似的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接