Ruby 中的排列 - 不确定如何处理

Permutations in Ruby - unsure how to handle

我正在尝试输出一个包含 1 和 2 排列的 10 元素数组,例如:

 [[1,1,1,1,1,1,1,1,1,1],
 [1,1,1,1,1,1,1,1,1,2],
 [1,2,1,2,1,2,1,2,1,2],
 ...etc...
 [2,2,2,2,2,2,2,2,2,2]]

我用较小的数组完成了这个,但是用 (10):

 a = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2]
 a = a.permutation(10).to_a
 print a.uniq

...这显然是一个太大的计算 - 一个小时后 运行,它还没有完成并且 ruby 进程占用了 12GB 的内存。还有其他方法吗?

您采用的方法确实产生了大量数据 - 20!/10!或大约 6000 亿个数组。当你想要的输出只有 1024 个数组时,这显然是非常浪费的

您所追求的更接近数组

上的product方法
[1,2].product(*([[1,2]]*9))

product 方法通过从每个接收者及其参数中选取一个元素来生成所有可能的组合。在array上使用splats和*方法只是为了避免写[1,2] 9次。

首先,检查该排列的大小

a.permutation(10).size
 => 670442572800

它很大。您可以做的是在较小的数组上使用 Array#repeated_permutations 。在这里查看:

b = [1, 2] # Only unique elements
b.repeated_permutation(10) # Returns enumerable
b.repeated_permutation(10).to_a # Creates array with all of permutations

这些排列已经是唯一的(你可以通过打印它的大小来检查有无 Array#uniq

编辑:不要使用这个...它比 运行 慢了一个因子(慢 10 倍):b= [1,2]; b.repeated_permutation(10).to_a

你要找的其实是一个二元置换数组...

所以采用这种方法,我们知道我们有 2**10 (== 1024) 个排列,它们转换为 0 到 1023 之间的数字。

试试这个:

1024.times.with_object([]) {|i, array| array << ( ("%10b" % (i-1) ).unpack("U*").map {|v| (v == 49) ? 2 : 1}  ) }

或者这个(稍微快一点):

(0..1023).each.with_object([]) {|i, array| array << ( (0..9).each.with_object([]) {|j, p| p << (i[j] +1)}  ) }

您选择的选项数量为 - 1024。对于每个选项 (i),您分配一个数字 (i-1) 并提取包含该数字的二进制代码。

在我的示例中,通过使用 "%10b" % (i-1) 将二进制代码转换为 10 位长的字符串,然后将该字符串解压缩为数组来提取二进制代码。我映射该数组,将我从字符串 (white space == 32 && zero == 48) 中获取的值替换为数字 1 或从 (the number 1 == 49) 中获取的值替换为数字 2.

瞧。

应该有更好的方法来提取数字的二进制表示,但我想不出一个,因为我 运行 睡得很少。

这是与@Myst 采用的方法类似的另一种方法,不同之处在于我使用 Fixnum#to_s 将整数转换为其二进制等价物的字符串表示形式。

当每个数字等于 1 或 2(或等于 0 或 1)时,有 2**n 个具有 n 位(包括前导零)的整数。因此,我们可以通过转换 02**n-1 之间的每个整数 i 1-1 映射到仅包含数字 12 的整数之一=] 位到 11 位到 2.

在Ruby中,将123转换为二进制的一种方法是:

123.to_s(2).to_i      #=> 1111011

作为

2**9 #=> 512 
(2**9-1).to_s(2)      #=> "111111111"
(2**9-1).to_s(2).to_i #=> 111111111

我们看到,当比较 0511 (2**9-1) 之间的数字时,123 的二进制表示将有两个前导零。由于我们需要那些前导零来转换为 12,因此将每个数字的二进制表示形式保留为字符串并用零填充字符串会很方便:

str = 123.to_s(2).rjust(9,'0') #=> "001111011"

这让我们可以写:

str.each_char.map { |c| c.eql?("0") ? 1 : 2 } }
  #=> [1, 1, 2, 2, 2, 2, 1, 2, 2]

我们可以将其包装在一个方法中:

def combos(n)
  (2**n).times.map { |i| i.to_s(2)
                          .rjust(n,'0')
                          .each_char
                          .map { |c| c.eql?("0") ? 1 : 2 } }
end

combos(1)
  #=> [[1], [2]] 
combos(2)
  #=> [[1, 1], [1, 2], [2, 1], [2, 2]] 
combos(3)
  #=> [[1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2],
  #    [2, 1, 1], [2, 1, 2], [2, 2, 1], [2, 2, 2]] 
combos(4)
  #=> [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 1], [1, 1, 2, 2],
  #    [1, 2, 1, 1], [1, 2, 1, 2], [1, 2, 2, 1], [1, 2, 2, 2],
  #    [2, 1, 1, 1], [2, 1, 1, 2], [2, 1, 2, 1], [2, 1, 2, 2],
  #    [2, 2, 1, 1], [2, 2, 1, 2], [2, 2, 2, 1], [2, 2, 2, 2]] 
combos(10).size
  #=> 1024