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

Posted

技术标签:

【中文标题】如何在 Ruby 中使用循环输出所有可能的组合?【英文标题】:How to output all possible combinations using loops in Ruby? 【发布时间】:2014-12-24 09:07:47 【问题描述】:

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

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

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"]

【问题讨论】:

使用数组排列怎么样? apidock.com/ruby/Array/permutation 对不起,没有读完。我看到你想写一个自定义的。祝你好运! 您可能不得不采用递归方法。 【参考方案1】:

这个实现就像用二进制递归计数:

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) 第一个元素与以下元素的组合相结合

【讨论】:

微小的遗漏,很容易修复:没有[]。将删除此评论。 @CarySwoveland 你能想出一种方法在结果中包含[] 而不会使代码量几乎翻倍吗?我不能! 一种简单的方法是像我在添加的基准测试中所做的那样,我创建了一个调用permutations 的方法,然后将[] 添加到返回的数组中。顺便说一句,permutations 在这里不是一个好名字,因为您正在计算组合。 (如果[1,2,3] 是排列的一个元素,那么[1,3,2][2,1,3][2,3,1][3,1,2][3,2,1] 也是。) @CarySwoveland 关于排列与组合的好点——高中数学太早了!关于[]:我正在寻找一种方法来改变算法,使其自然地包含[],但它仍然让我无法理解。【参考方案2】:

这里是递归算法

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"]]

【讨论】:

您需要一个小修复:p combinations(a, 0) =&gt; [["A"]]【参考方案3】:

我可以想出一种方法来计算给定大小的组合而不使用递归,但它并不是特别有效。但是,如果您想获得所有大小的所有组合(有时称为“幂”),它非常有效。 [编辑:显然不是。查看基准。]我的理解是问题与后者有关,但我会为每个问题提供方法。

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

代码

def all_combos(sz)
  [*(0..2**sz-1)].map  |i| ("%0#szb" % 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

示例

input = %wb 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"]]

【讨论】:

【参考方案4】:

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

比较方法

module Methods
  def ruby(array)
    (0..array.size).each_with_object([])  |i,a|
       a.concat(array.combination(i).to_a) 
  end

  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

  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

  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#szb" % i).chars 
                 .map  |a| a.each_with_index.select  |n,ndx| n=="1" .map(&:last) 
  end
end

测试数据

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

帮助者

def compute(arr, meth)
  send(meth, arr)
end  

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

包含模块

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

确认方法返回相同的值

arr = test_array(8)

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

基准代码

require 'benchmark'

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

[10, 15, 20].each do |n|
  puts "\nn = #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

测试(秒)

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)

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

【讨论】:

干得好!看看不同方法的表现总是很有趣。对于咧嘴笑,与Array#combination 的本机实现进行比较怎么样? (您可能需要增加 n 才能注册某些内容。) 好主意。我加了。

以上是关于如何在 Ruby 中使用循环输出所有可能的组合?的主要内容,如果未能解决你的问题,请参考以下文章

从Ruby中的列表中获取所有对的组合

如何在 Python 中创建多个 for 循环列表的递归以获得组合? [复制]

ruby - 如何从字符串数组中生成可能的字母顺序组合?

使用嵌套循环迭代 m^n 组合[关闭]

Scala:如何在循环中组合数据帧

如何使用 for 循环组合多个数据帧?