繁体   English   中英

Ruby:将数组划分为 2 个具有最接近平均值的数组

[英]Ruby: Divide array into 2 arrays with the closest possible average

背景:我正在为一个小型多人视频游戏项目开发“匹配系统”。 每个球员的排名从 0 到 10,每支球队有 4 名球员。 我试图找到一种平衡球队的好方法,使他们的平均排名尽可能接近,比赛尽可能公平。

我目前的,有缺陷的方法是这样的:

def create_teams(players)
    teams = Hash.new{|hash, team| hash[team] = []}

    players.sort_by(&:rank).each_slice(2) do |slice|
        teams[:team1] << slice[0]
        teams[:team2] << slice[1]
    end

    teams
end

如果排名已经非常相似,这会很好地工作,但这不是解决这个问题的正确方法。 例如,它在这样的情况下失败:

require "ostruct"

class Array
    def avg
        sum.fdiv(size)
    end
end

dummy_players = [9, 5, 5, 3, 3, 3, 2, 0].map{|rank| OpenStruct.new(rank: rank)}

teams = create_teams(dummy_players)
teams.each do |team, players|
    ranks = players.map(&:rank)
    puts "#{team} - ranks: #{ranks.inspect}, avg: #{ranks.avg}"
end

这导致了非常不公平的团队:

team1 - ranks: [0, 3, 3, 5], avg: 2.75
team2 - ranks: [2, 3, 5, 9], avg: 4.75

相反,我希望这种情况下的团队是这样的:

team1 - ranks: [0, 3, 3, 9], avg: 3.75
team2 - ranks: [2, 3, 5, 5], avg: 3.75

如果有 n 个玩家,其中 n 是偶数,则有

C(n) = n!/((n/2)!(n/2)!)

将 n 个玩家分成两队 n/2 个玩家的方法,其中 n! 等于 n 阶乘。 这通常表示为从 n 个项目的集合中选择 n/2 个项目的方法数。

要获得在总等级(以及平均等级)中具有最小绝对差异的分区,必须枚举所有 C(n) 个分区。 如果 n = 8,如本例所示,C(8) = 70(例如,请参见此在线计算器)。 但是,如果 n = 16,则 C(16) = 12,870 且 C(32) = 601,080,390。 这让您了解 n 必须有多小才能执行完整的枚举。

如果 n 太大而无法枚举所有组合,则必须求助于使用启发式或主观规则来划分等级数组。 这里有两种可能:

  • 将等级最高的元素(“等级 1”)分配给 A 队,将等级 2 和 3 的元素分配给 B 队,将等级 4 和 5 的元素分配给 A 队,以此类推。
  • 将秩为 1 和 n 的元素分配给 A 组,将秩为 2 和 n-1 的元素分配给 B 组,依此类推。

启发式的问题在于评估它们的有效性。 对于这个问题,对于您设计的每个启发式方法,都有一系列等级,启发式方法的性能非常糟糕。 如果您知道可能的秩数组的范围并且有一种绘制无偏样本的方法,则可以统计地评估启发式。 然而,这通常是不可能的。

以下是检查所有分区的方法。 认为:

ranks = [3, 3, 0, 2, 5, 9, 3, 5] 

那么我们就可以进行下面的计算了。

indices = ranks.size.times.to_a
  #=> [0, 1, 2, 3, 4, 5, 6, 7] 
team_a = indices.combination(ranks.size/2).min_by do |combo|
   team_b = indices - combo
   (combo.sum { |i| ranks[i] } - team_b.sum { |i| ranks[i] }).abs
end
  #=> [0, 1, 2, 5]
team_b = indices - team_a
  #=> [3, 4, 6, 7]

请参阅Array#combinationEnumerable#min_by

我们看到 A 队球员的排名:

arr = ranks.values_at(*team_a)
  #=> [3, 3, 0, 9]

这些等级的总和是:

arr.sum
  #=> 15

同样,对于 B 队:

arr = ranks.values_at(*team_b)
  #=> [2, 5, 3, 5] 
arr.sum
  #=> 15 

请参阅Array#values_at

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM