Ruby 如何获得';公平组合';从n个元素的数组中?
在Ruby上使用Ruby 如何获得';公平组合';从n个元素的数组中?,ruby,algorithm,math,combinations,Ruby,Algorithm,Math,Combinations,在Ruby上使用composition方法 [1, 2, 3, 4, 5, 6].combination(2).to_a #=> [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3], # [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6], # [4, 5], [4, 6], [5, 6]] 我们可以得到一个包含15(6C2)个元素的二维数组 我想创建一个fair_composition方法
composition
方法
[1, 2, 3, 4, 5, 6].combination(2).to_a
#=> [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3],
# [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6],
# [4, 5], [4, 6], [5, 6]]
我们可以得到一个包含15(6C2)个元素的二维数组
我想创建一个fair_composition
方法,该方法返回如下数组:
arr = [[1, 2], [3, 5], [4, 6],
[3, 4], [5, 1], [6, 2],
[5, 6], [1, 3], [2, 4],
[2, 3], [4, 5], [6, 1],
[1, 4], [2, 5], [3, 6]]
每三个子数组(6的一半)包含所有给定元素:
arr.each_slice(3).map { |a| a.flatten.sort }
#=> [[1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6]]
这使得它有点“公平”,通过使用尽可能不同的数组元素
为了使其更具一般性,它需要满足以下要求:
(1) 当您从一开始就跟随数组并计算每个数字出现的次数时,在任何一点上,它都应该尽可能平坦
(1..7).to_a.fair_combination(3)
#=> [[1, 2, 3], [4, 5, 6], [7, 1, 4], [2, 5, 3], [6, 7, 2], ...]
前7个数字构成[1,2,…,7],接下来的7个数字也是如此
(2) 一旦数字A与B位于同一数组中,如果可能,A不希望与B位于同一数组中
(1..10).to_a.fair_combination(4)
#=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 1, 5], [2, 6, 9, 3], [4, 7, 10, 8], ...]
有什么好的算法可以创建这样的“公平组合”吗?Naỉve实现是:
class Integer
# naïve factorial implementation; no checks
def !
(1..self).inject(:*)
end
end
class Range
# constant Proc instance for tests; not needed
C_N_R = -> (n, r) { n.! / ( r.! * (n - r).! ) }
def fair_combination(n)
to_a.permutation
.map { |a| a.each_slice(n).to_a }
.each_with_object([]) do |e, memo|
e.map!(&:sort)
memo << e if memo.all? { |me| (me & e).empty? }
end
end
end
▶ (1..6).fair_combination(2)
#⇒ [
# [[1, 2], [3, 4], [5, 6]],
# [[1, 3], [2, 5], [4, 6]],
# [[1, 4], [2, 6], [3, 5]],
# [[1, 5], [2, 4], [3, 6]],
# [[1, 6], [2, 3], [4, 5]]]
▶ (1..6).fair_combination(3)
#⇒ [
# [[1, 2, 3], [4, 5, 6]],
# [[1, 2, 4], [3, 5, 6]],
# [[1, 2, 5], [3, 4, 6]],
# [[1, 2, 6], [3, 4, 5]],
# [[1, 3, 4], [2, 5, 6]],
# [[1, 3, 5], [2, 4, 6]],
# [[1, 3, 6], [2, 4, 5]],
# [[1, 4, 5], [2, 3, 6]],
# [[1, 4, 6], [2, 3, 5]],
# [[1, 5, 6], [2, 3, 4]]]
▶ Range::C_N_R[6, 3]
#⇒ 20
类整数
#naive阶乘实现;没有支票
def!
(1..self).注入(:*)
结束
结束
等级范围
#用于测试的常量Proc实例;不需要
C_N_R=->(N,R){N.!/(R.!*(N-R.!)}
def公平组合(n)
排列
.map{a|a.each_slice(n).to_a}
.每个带有对象([])的对象都要做,备注|
e、 地图!(&:排序)
备忘录8)
要将其调整为更健壮的解决方案,需要摆脱置换
,转而采用“智能串联置换数组”
希望这对初学者有好处 它不能保证提供最好的解决方案,但它提供了一个足够好的解决方案
在每一步中,它都会选择一个最小子库,该子库是具有最小高度的项目集,对于该子库,仍然有一个组合可供选择(高度是这些项目以前使用过的次数)
例如,让枚举数为
my_enum = FairPermuter.new('abcdef'.chars, 4).each
第一次迭代可能会返回
my_enum.next # => ['a', 'b', 'c', 'd']
此时,这些字母的高度为1,但没有足够的高度为0的字母进行组合,因此将所有字母作为下一个字母:
my_enum.next # => ['a', 'b', 'c', 'e'] for instance
现在的高度是a
的2
,b
和c
,d
和e
的1
,以及f
的0
,而最佳池仍然是完整的初始集
因此,对于大尺寸的组合,这并不是真正的优化。另一方面,如果组合的大小最多是初始集大小的一半,则该算法相当不错
class FairPermuter
def initialize(pool, size)
@pool = pool
@size = size
@all = Array(pool).combination(size)
@used = []
@counts = Hash.new(0)
@max_count = 0
end
def find_valid_combination
[*0..@max_count].each do |height|
candidates = @pool.select { |item| @counts[item] <= height }
next if candidates.size < @size
cand_comb = [*candidates.combination(@size)] - @used
comb = cand_comb.sample
return comb if comb
end
nil
end
def each
return enum_for(:each) unless block_given?
while combination = find_valid_combination
@used << combination
combination.each { |k| @counts[k] += 1 }
@max_count = @counts.values.max
yield combination
return if @used.size >= [*1..@pool.size].inject(1, :*)
end
end
end
2对6的公平组合结果
[[4, 6], [1, 3], [2, 5],
[3, 5], [1, 4], [2, 6],
[4, 5], [3, 6], [1, 2],
[2, 3], [5, 6], [1, 6],
[3, 4], [1, 5], [2, 4]]
2对5的公平组合结果
[[4, 5], [2, 3], [3, 5],
[1, 2], [1, 4], [1, 5],
[2, 4], [3, 4], [1, 3],
[2, 5]]
获得5对12组合的时间:
1.19 real 1.15 user 0.03 sys
我讨厌成为那个家伙,但你试过什么?@NickZuber谢谢你的评论。设法创造一种公平组合的等价物(2);但是效果不太好&不能让它更一般。谢谢你的评论。非常抱歉,我本应该说明条件的。只是具体描述了这种方法应该满足的要求。但似乎只要输入数组的大小(如12)可以除以参数(如2,3,4,6),你的算法就可以运行得很好。我想我可以从你的代码开始重新思考。干杯,谢谢你的密码。这绝对是一个好算法。我想我会修改它来处理数组大小不够大的问题。顺便说一句,我很确定,即使在严格拆分组合(如2/6组合)的受限情况下,如果拆分不是整个数组的一半,而不使用完全回溯,也没有简单的算法。
1.19 real 1.15 user 0.03 sys