如何在 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) => [["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 中使用循环输出所有可能的组合?的主要内容,如果未能解决你的问题,请参考以下文章