Python的itertools.product在Ruby中的等价物是什么?

4
我正在寻找一种在Ruby中具有与Python的itertools.product相同效果的方法。请看以下Python代码:
from itertools import product

chars = []
for i in range(97,123):
    chars.append(chr(i))

for a in range(1,3):
    for i in product(chars,repeat=a):
        s = ''.join(i)
        print s

那会输出如下内容:
a, b, c... x, y, z, aa, ab, ac... ax, ay, az, ba, bb, bc.. etc.

我尝试将其翻译为Ruby代码:
(1..2).each do |n|
  ('a'..'z').to_a.combination(n).each do |c|
    s = c.join
    puts s
  end
end

但输出结果并不相同。一个字符的正则表达式可以正常工作(a-z),但当它变成两个字符时,就不能按我期望的方式工作:

ab, ac, ad.. ax, ay, az, bc, bd, be

它没有生成aababb - 所以看起来它生成了所有不重复字符组合吗?

那么我应该使用什么方法来生成所有的组合,就像Python中的itertools.product一样?


Array#productitertools.product 之间的关键区别在于 Ruby 的是一个方法而不是函数。这通常是一种不便。 - tokland
6个回答

3

我尝试过了,但无法使其工作 - 你能否给出一个示例,以便输出与我的Python代码相同? - Alex Coplan
1
在 Ruby 中,使用 itertools.product 文档中给出的示例(_product('ABCD', 'xy')_)可能类似于 _%w{ A B C D }.product(%w{ x y }).map(&:join)_。 - luis.parravicini
1
我的意思是使用类似于Python示例中的重复功能,product(chars,repeat=a)(其中a为1或2)- 如果要生成每个5个字符组合怎么办? - 在Python中,您可以执行product(chars,repeat=5) - 那么在Ruby中该如何实现呢? - Alex Coplan

3

我会编写以下代码(仅简化为3个元素,需要Ruby 1.9):

xs = ["a", "b", "c"]
strings = 1.upto(xs.size).flat_map do |n| 
  xs.repeated_permutation(n).map(&:join)
end
#=> ["a", "b", "c", "aa", "ab", "ac", ...,  "cca", "ccb", "ccc"]

一种懒惰的解决方案:你可以很容易地使用each代替map来编写它,但让我们检查一下Ruby 2.0中的“懒惰”:
xs = ("a".."z").to_a
strings = 1.upto(xs.size).lazy.flat_map do |n| 
  xs.repeated_permutation(n).lazy.map(&:join)
end

工作得很好,但有没有办法将其变成生成器?- 使用6个字符的a-z字符串会占用所有的RAM! - Alex Coplan
1
对于一种懒惰的解决方案:1)使用each而不是maps。2)尝试Ruby 2.0中即将出现的懒惰模式:http://bugs.ruby-lang.org/attachments/1803/lazy.rb。在flat_map和map之前添加lazy,然后对表达式进行each操作。 - tokland

2

神奇(虽然不是很好看):

a = ('a'..'z').to_a
result = (0..2).map { |n| 
  a.product(*n.times.inject([]) { |s,x| s << a }) }.map { |x| x.map(&:join) } 
}

puts result

解释:为了让Python的product函数正常工作,你需要在product函数的参数中重复数组n-1次。

因此,product('abc', repeat=n)在Ruby中相当于:

a = ['a','b','c']
a.product()     # n = 1
a.product(a)    # n = 2
a.product(a, a) # n = 3

上述代码中,恶意的inject函数会构建一个“参数数组”。虽然它可以自动完成,但代码效率并不高,因此不要试图用它来构建大型“产品”。


谢谢!- 看起来没有Python的repeat参数的内置等效物,是吗? - Alex Coplan
@AlexCoplan 没有直接等价物,至少在标准库中我找不到。 - Casper
@AlexCoplan然而,使用a.product(* [a] * n),你可以得到非常接近的结果。其中,n等同于Python中的repeat = n - Casper
有一个名为Array#repeated_combination的函数。 - steenslag
@steenslag - 不完全相同,请看我对你回答的评论。 - Alex Coplan
本应该是“repeated_permutation”。现在 Tokland 已经有了。 - steenslag

1
在 Ruby 中,Array#product 会返回一个笛卡尔积。将原始数组添加进去也会得到相同的结果。
ar = (?a..?z).to_a
ar + ar.product(ar).map(&:join)

1

在我写完这个之后,我注意到Casper的解决方案基本上是一样的。有些人可能会觉得这个更易读,所以我保留它。

arr = ['a', 'b', 'c']

p (0..2).inject([]) { |acc, a|
  acc + arr.product(*[arr]*a).map(&:join)
}

=> ["a", "b", "c", "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc", "aaa", "aab", "aac", "aba", "abb", "abc", "aca", "acb", "acc", "baa", "bab", "bac", "bba", "bbb", "bbc", "bca", "bcb", "bcc", "caa", "cab", "cac", "cba", "cbb", "cbc", "cca", "ccb", "ccc"]

需要注意的关键点包括:

  • *[arr]*a,首先创建一个由aarr组成的数组,然后将其展开为product方法的a个参数。
  • map(&:join),这是map{|e| e.join}的简写形式。
  • inject(又称“reduce”,来自“map-reduce”之名),是FP的支柱之一。

不错啊,比我的好多了 :) - Casper

0

在tokland的帮助下,我搞定了:

(1..2).each do |n|
  ('a'..'z').to_a.repeated_permutation(n).each do |a|
    s = a.join
    puts s
  end
end

而且它是懒加载的,因此在使用它生成较长字符串时不会占用过多内存。


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