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