I'm looking for a method that has the same effect as Python's itertools.product
in Ruby. Take the following Python code:
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
That outputs something like this:
a, b, c... x, y, z, aa, ab, ac... ax, ay, az, ba, bb, bc.. etc.
I tried to translate that into Ruby:
(1..2).each do |n|
('a'..'z').to_a.combination(n).each do |c|
s = c.join
puts s
end
end
But the output isn't the same. The one-character ones work fine (az), but when it goes into two-character ones, it doesn't work as I expected:
ab, ac, ad.. ax, ay, az, bc, bd, be
It isn't generating aa
, ba
or bb
- so it appears it's generating all the combinations without repeating characters or something?
So what method should I use to generate all the combinations like itertools.product
does in Python?
您拥有类似于itertools.product的 Array#product 。
I would write (simplified for 3 elements, Ruby 1.9 required):
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"]
A lazy solution: you could easily write it with each
s instead of map
s, but let's check "lazy" from 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
Magic (not very pretty though):
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
Explanation: in order to work as the python product
you need to repeat the array n-1
times in the product
arguments.
So product('abc', repeat=n)
is the same in ruby as:
a = ['a','b','c']
a.product() # n = 1
a.product(a) # n = 2
a.product(a, a) # n = 3
That's what the nasty inject
does in the above code. It builds such an "argument array" automatically. It's not very efficient code though, so don't try to build large "products" with it.
After I wrote this, I noticed Casper's solution which is essentially the same. Some might find this one more readable so I am leaving it..
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"]
Key "gotchas" would be
*[arr]*a
, which first creates an array of a
arr
s, then splats it into a
arguments of product
method. map(&:join)
, which is a shorthand for map{|e| e.join}
map{|e| e.join}
inject
(aka "reduce", from "map-reduce" fame), one of the FP pillars In ruby, Array#product results in a Cathesian product. Adding the original array would yield the same results.
ar = (?a..?z).to_a
ar + ar.product(ar).map(&:join)
With the help of tokland, I've got it:
(1..2).each do |n|
('a'..'z').to_a.repeated_permutation(n).each do |a|
s = a.join
puts s
end
end
And it's lazy, so it doesn't hog RAM when you're using it to generate longer strings.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.