如何在Ruby中使用循环输出所有可能的组合?

How to output all possible combinations using loops in Ruby?

我刚刚开始学习编程,并且正在尝试编写一个输出所有可能组合的函数。 到目前为止,我已经能够找到所有可能的大小为2的组合,但我不知道如何保持代码开放以处理更大尺寸的组合。 某种递归会有用吗?

我知道我可以使用内置的组合方法,但我只想弄清楚如何从头开始编写它。 任何建议将不胜感激。 谢谢!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def two_combos(input)
    list = []
    for index1 in (0...input.length)
        for index2 in (0...input.length)
            if input[index1] != input[index2]
                if list.include?([input[index2],input[index1]])==false
                    list << [input[index1],input[index2]]
                end
            end
        end
    end
    return list
end


two_combos(["A","B","C"])
#outputs
=> [["A","B"], ["A","C"], ["B","C"]]
#Missing
["A","B","C"]


这个实现类似于二进制递归计数:

1
2
3
4
5
6
7
8
9
def combinations(items)
  return [] unless items.any?
  prefix = items[0]
  suffixes = combinations(items[1..-1])
  [[prefix]] + suffixes + suffixes.map {|item| [prefix] + item }
end

> combinations(%w(a b c))
=> [["a"], ["b"], ["c"], ["b","c"], ["a","b"], ["a","c"], ["a","b","c"]]

在每个阶段,组合是以下的串联:

  • 仅第一个元素
  • 以下元素的组合(元素1..n-1)
  • 第一个元素与以下元素的组合相结合


我可以想到一种在不使用递归的情况下计算给定大小的组合的方法,但它并不是特别有效。但是,如果您想获得所有尺寸的所有组合(有时称为"功率"),则效率非常高。 [编辑:显然不是。参见基准。]我的理解是问题涉及后者,但我将为每个人提供方法。

如果index具有n个元素,则每个组合可以由n元素数组表示,其元素分别为0或1,1表示组合包含该索引处的元素,'0'(或者领先的空间)意味着它没有。因此,我们可以通过简单地生成长度为n的所有二进制数来生成所有大小的所有组合的集合,将每个从其字符串表示(带有前导零)转换为"0""1" s的数组,用它们的索引位置替换"1",删除"0"并在给定的索引位置提取index元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
def all_combos(sz)
  [*(0..2**sz-1)].map { |i| ("%0#{sz}b" % i).chars }
                 .map { |a| a.each_with_index
                             .select { |n,ndx| n=="1" }.map(&:last) }
end

def combos(input, n, all_combos)
  all_combos.select { |c| c.size == n }.map { |c| input.values_at(*c) }
end

def power(input, all_combos)
  all_combos.map { |c| input.values_at(*c) }
end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
input = %w{b e a r s}
   #=> ["b","e","a","r","s"]
ac = all_combos(input.size)
  #=> [[], [4], [3], [3, 4], [2], [2, 4], [2, 3], [2, 3, 4],
  #    [1], [1, 4], [1, 3], [1, 3, 4], [1, 2], [1, 2, 4], [1, 2, 3],
  #    [1, 2, 3, 4], [0], [0, 4], [0, 3], [0, 3, 4], [0, 2], [0, 2, 4],
  #    [0, 2, 3], [0, 2, 3, 4], [0, 1], [0, 1, 4], [0, 1, 3], [0, 1, 3, 4],
  #    [0, 1, 2], [0, 1, 2, 4], [0, 1, 2, 3], [0, 1, 2, 3, 4]]

(0..input.size).each { |i| puts"size #{i}"; p combos(input, i, ac) }
  # size 0
  # [[]]
  # size 1
  # [["s"], ["r"], ["a"], ["e"], ["b"]]
  # size 2
  # [["r","s"], ["a","s"], ["a","r"], ["e","s"], ["e","r"],
  #    ["e","a"], ["b","s"], ["b","r"], ["b","a"], ["b","e"]]
  # size 3
  # [["a","r","s"], ["e","r","s"], ["e","a","s"], ["e","a","r"],
  #    ["b","r","s"], ["b","a","s"], ["b","a","r"], ["b","e","s"],
  #    ["b","e","r"], ["b","e","a"]]
  # size 4
  # [["e","a","r","s"], ["b","a","r","s"], ["b","e","r","s"],
  #    ["b","e","a","s"], ["b","e","a","r"]]
  # size 5
  # [["b","e","a","r","s"]]

power(input, ac)
  #=> [[], ["s"], ["r"], ["r","s"], ["a"], ["a","s"], ["a","r"],
  #    ["a","r","s"], ["e"], ["e","s"], ["e","r"], ["e","r","s"],
  #    ["e","a"], ["e","a","s"], ["e","a","r"], ["e","a","r","s"],
  #    ["b"], ["b","s"], ["b","r"], ["b","r","s"], ["b","a"],
  #    ["b","a","s"], ["b","a","r"], ["b","a","r","s"], ["b","e"],
  #    ["b","e","s"], ["b","e","r"], ["b","e","r","s"], ["b","e","a"],
  #    ["b","e","a","s"], ["b","e","a","r"], ["b","e","a","r","s"]]


这是递归算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def combinations(array, size)
  fail"size is too big" if size > array.size

  combination([], [], array, size)
end

def combination(result, step, array, size)
  steps = size - step.size
  array[0..-steps].each_with_index do |a, i|
    next_step = step + [a]
    if next_step.size < size
      combination(result, next_step, array[i+1..-1], size)
    else
      result << next_step
    end
  end
  result
end

a = ("A".."E").to_a
p combinations(a, 1)
# [["A"], ["B"], ["C"], ["D"], ["E"]]
p combinations(a, 2)
# [["A","B"], ["A","C"], ["A","D"], ["A","E"], ["B","C"], ["B","D"], ["B","E"], ["C","D"], ["C","E"], ["D","E"]]
p combinations(a, 3)
# [["A","B","C"], ["A","B","D"], ["A","B","E"], ["A","C","D"], ["A","C","E"], ["A","D","E"], ["B","C","D"], ["B","C","E"], ["B","D","E"], ["C","D","E"]]
p combinations(a, 4)
# [["A","B","C","D"], ["A","B","C","E"], ["A","B","D","E"], ["A","C","D","E"], ["B","C","D","E"]]


先生们,启动你的引擎吧!

方法比较

1
2
3
4
5
module Methods
  def ruby(array)
    (0..array.size).each_with_object([]) { |i,a|
       a.concat(array.combination(i).to_a) }
  end

1
2
3
4
5
6
7
8
9
10
11
12
  def todd(input)
    permutations(input) << []
  end

  private

  def permutations(items)
    return [] unless items.any?
    prefix = items[0]
    suffixes = permutations(items[1..-1])
    [[prefix]] + suffixes + suffixes.map {|item| [prefix, item].flatten }
  end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  public

  def fl00r(array)
    (1..array.size).each_with_object([]) { |i,a|
       a.concat(combinations(array, i)) } << []
  end

  private

  def combinations(array, size)
    fail"size is too big" if size > array.size
    combination([], [], array, size)
  end

  def combination(result, step, array, size)
    steps = size - step.size
    array[0..-steps].each_with_index do |a, i|
      next_step = step + [a]
      if next_step.size < size
        combination(result, next_step, array[i+1..-1], size)
      else
        result << next_step
      end
    end
    result
  end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  public

  def cary(input)
    ac = all_combos(input.size)
    ac.map { |c| input.values_at(*c) }
  end

  private

  def all_combos(sz)
    [*0..2**sz-1].map { |i| ("%0#{sz}b" % i).chars }
                 .map { |a| a.each_with_index.select { |n,ndx| n=="1" }.map(&:last) }
  end
end

测试数据

1
2
3
def test_array(n)
  [*1..n]
end

助手

1
2
3
4
5
6
7
def compute(arr, meth)
  send(meth, arr)
end  

def compute_sort(arr, meth)
  compute(arr, meth).map(&:sort).sort
end

包含模块

1
2
3
include Methods
@methods = Methods.public_instance_methods(false)
  #=> [:ruby, :todd, :fl00r, :cary]

确认方法返回相同的值

1
2
3
4
5
arr = test_array(8)

a = compute_sort(arr, @methods.first)
puts @methods[1..-1].all? { |m| a == compute_sort(arr, m) }
  #=> true

基准代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'benchmark'

@indent = methods.map { |m| m.to_s.size }.max

[10, 15, 20].each do |n|
  puts"
n = #{n}"

  arr = test_array(n)
  Benchmark.bm(@indent) do |bm|
    @methods.each do |m|
      bm.report m.to_s do
        compute(arr, m).size
      end
    end
  end
end

测试(秒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
n = 10
                                 user     system      total        real
ruby                         0.000000   0.000000   0.000000 (  0.000312)
todd                         0.000000   0.000000   0.000000 (  0.001611)
fl00r                        0.000000   0.000000   0.000000 (  0.002675)
cary                         0.010000   0.000000   0.010000 (  0.010026)

n = 15
                                 user     system      total        real
ruby                         0.010000   0.000000   0.010000 (  0.010742)
todd                         0.070000   0.010000   0.080000 (  0.081821)
fl00r                        0.080000   0.000000   0.080000 (  0.076030)
cary                         0.430000   0.020000   0.450000 (  0.450382)

n = 20
                                 user     system      total        real
ruby                         0.310000   0.040000   0.350000 (  0.350484)
todd                         2.360000   0.130000   2.490000 (  2.487493)
fl00r                        2.320000   0.090000   2.410000 (  2.405377)
cary                        21.420000   0.620000  22.040000 ( 22.053303)

我只得出一个明确的结论。