Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby 如何获得';公平组合';从n个元素的数组中?_Ruby_Algorithm_Math_Combinations - Fatal编程技术网

Ruby 如何获得';公平组合';从n个元素的数组中?

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方法

在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
方法,该方法返回如下数组:

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
类整数
#nai’ve阶乘实现;没有支票
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