简体   繁体   English

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

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

Background: I'm working on a "matchmaking system" for a small multiplayer video game side project.背景:我正在为一个小型多人视频游戏项目开发“匹配系统”。 Every player has a rank from 0-10, every team has 4 players.每个球员的排名从 0 到 10,每支球队有 4 名球员。 I'm trying to find a good way to balance out the teams so that the average rank of both of them is as close as possible and the match is as fair as possible.我试图找到一种平衡球队的好方法,使他们的平均排名尽可能接近,比赛尽可能公平。

My current, flawed approach looks like this:我目前的,有缺陷的方法是这样的:

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

This works decently well if the ranks are already pretty similar but it's not a proper solution to this problem.如果排名已经非常相似,这会很好地工作,但这不是解决这个问题的正确方法。 For example, it fails in a situation like this:例如,它在这样的情况下失败:

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

This results in pretty unfair teams:这导致了非常不公平的团队:

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

Instead, I'd like the teams in this situation to be like this:相反,我希望这种情况下的团队是这样的:

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

If there are n players, where n is an even number, there are如果有 n 个玩家,其中 n 是偶数,则有

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

ways to partition the n players into two teams of n/2 players, where n!将 n 个玩家分成两队 n/2 个玩家的方法,其中 n! equals n-facorial.等于 n 阶乘。 This is often expressed as the number of ways to choosing n/2 items from a collection of n items.这通常表示为从 n 个项目的集合中选择 n/2 个项目的方法数。

To obtain the partition that has a mimimum absolute difference in total ranks (and hence, in mean ranks), one would have to enumerate all C(n) partitions.要获得在总等级(以及平均等级)中具有最小绝对差异的分区,必须枚举所有 C(n) 个分区。 If n = 8, as in this example, C(8) = 70 (see, for example, this online calculator ).如果 n = 8,如本例所示,C(8) = 70(例如,请参见此在线计算器)。 If, however, n = 16, then C(16) = 12,870 and C(32) = 601,080,390.但是,如果 n = 16,则 C(16) = 12,870 且 C(32) = 601,080,390。 This gives you an idea of how small n must be in order perform a complete enumeration.这让您了解 n 必须有多小才能执行完整的枚举。

If n is too large to enumerate all combinations you must resort to using a heuristic , or a subjective rule for partitioning the array of ranks.如果 n 太大而无法枚举所有组合,则必须求助于使用启发式或主观规则来划分等级数组。 Here are two possibilities:这里有两种可能:

  • assign the highest rank element ("rank 1") to team A, assign elements with ranks 2 and 3 to team B, assign elements with ranks 4 and 5 to team A, and so on.将等级最高的元素(“等级 1”)分配给 A 队,将等级 2 和 3 的元素分配给 B 队,将等级 4 和 5 的元素分配给 A 队,以此类推。
  • assign elements with ranks 1 and n to team A, elements with ranks 2 and n-1 to team B, and so on.将秩为 1 和 n 的元素分配给 A 组,将秩为 2 和 n-1 的元素分配给 B 组,依此类推。

The trouble with heuristics is evaluating their effectiveness.启发式的问题在于评估它们的有效性。 For this problem, for every heuristic you devise there is an array of ranks for which the heuristic's performance is abysmal.对于这个问题,对于您设计的每个启发式方法,都有一系列等级,启发式方法的性能非常糟糕。 If you know the universe of possible arrays of ranks and have a way of drawing unbiased samples you can evaluate the heuristic statistically.如果您知道可能的秩数组的范围并且有一种绘制无偏样本的方法,则可以统计地评估启发式。 That generally is not possible, however.然而,这通常是不可能的。

Here is how you could examine all partitions.以下是检查所有分区的方法。 Suppose:认为:

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

Then we may perform the following calculations.那么我们就可以进行下面的计算了。

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]

SeeArray#combination andEnumerable#min_by .请参阅Array#combinationEnumerable#min_by

We see that team A players have ranks:我们看到 A 队球员的排名:

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

and the sum of those ranks is:这些等级的总和是:

arr.sum
  #=> 15

Similarly, for team B:同样,对于 B 队:

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

See Array#values_at .请参阅Array#values_at

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

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