簡體   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