8

これは、オフィス ホッケー プールで優勝するチャンスを最大限にしようと始めた、ちょっとした楽しいプロジェクトです。最高サラリーキャップ内で最も多くのポイントを獲得できる 20 人の選手を選ぶ最善の方法を見つけようとしています。

たとえば、生データが

  1. プレイヤー名
  2. ポジション(フォワード、ディフェンス、ゴールキーパー)
  3. 今シーズンの予想ポイント数
  4. 給料。

X サラリー キャップ内で最も多くのポイントを獲得できる 20 人のプレーヤーが必要です。後で、フェーズ 2 として同じことをしたいと思いますが、この場合、12 人のフォワード、6 人のディフェンス、2 人のゴールキーパーだけが必要です。

明らかな方法は、考えられるすべての組み合わせを単純に実行することですが、これは機能しますが、500 人のプレイヤーの場合のように有効なオプションではありません。考えられる組み合わせが多すぎます。いくつかのスマート フィルターを追加して、500 人のプレーヤーを上位 50 人のフォワード、上位 30 人のディフェンス、上位 15 人のゴールキーパーに減らすこともできますが、それでも、これは非常に遅いプロセスになります。

これを実装する他のアルゴリズムがあるかどうか疑問に思っています。これは単なる遊びであり、重要なビジネス リクエストではありません。しかし、今後の進め方についてご意見がございましたら、お気軽にお問い合わせください。

私の最初の試みは、他の情報源からの助けを借りて、ナップザック アルゴリズムを使用することでした。パラメータとして給与だけで動作するようです。20 プレイヤーのチーム パラメータを追加する方法を理解するのに苦労しています。.Net ですが、Java に簡単に変換できるはずです。

別のループを実行して、給与に関係なく 20 人のプレーヤーで最高のチームを見つけ出し、2 つのリストで最も高いチームが見つかるまで 2 つのリストを比較することを考えていました。わからない。

namespace HockeyPoolCalculator
{
public class ZeroOneKnapsack
{

protected List<Item> itemList = new List<Item>();
protected int maxSalary = 0;
protected int teamSize = 0;
protected int teamSalary = 0;
protected int points = 0;
protected bool calculated = false;

public ZeroOneKnapsack() { }

public ZeroOneKnapsack(int _maxSalary)
{
setMaxSalary(_maxSalary);
}

public ZeroOneKnapsack(List<Item> _itemList)
{
setItemList(_itemList);
}

public ZeroOneKnapsack(List<Item> _itemList, int _maxSalary)
{
setItemList(_itemList);
setMaxSalary(_maxSalary);
}

// calculte the solution of 0-1 knapsack problem with dynamic method:
public virtual List<Item> calcSolution()
{
int n = itemList.Count;

setInitialStateForCalculation();
if (n > 0 && maxSalary > 0)
{
List<List<int>> playerList = new List<List<int>>();
List<int> salaryList = new List<int>();

//initialise list
playerList.Add(salaryList);
for (int j = 0; j <= maxSalary; j++)
salaryList.Add(0);
// Loop through players
for (int i = 1; i <= n; i++)
{
List<int> prev = salaryList;
playerList.Add(salaryList = new List<int>());
for (int j = 0; j <= maxSalary; j++)
{
if (j > 0)
{
int wH = itemList.ElementAt(i - 1).getSalary();
// Is the players salary more than the current calculated salary? If yes, then keep current max points, else get the highest amount between previous max points at that salary and new max points.
salaryList.Add((wH > j)?prev.ElementAt(j): Math.Max(prev.ElementAt(j),itemList.ElementAt(i - 1).getPoints() + prev.ElementAt(j - wH)));
}
else
{
salaryList.Add(0);
}
} // for (j...)
} // for (i...)
points = salaryList.ElementAt(maxSalary);

for (int i = n, j = maxSalary; i > 0 && j >= 0; i--)
{
int tempI = playerList.ElementAt(i).ElementAt(j);
int tempI_1 = playerList.ElementAt(i - 1).ElementAt(j);
if ((i == 0 && tempI > 0)||(i > 0 && tempI != tempI_1))
{
Item iH = itemList.ElementAt(i - 1);
int wH = iH.getSalary();
iH.setInKnapsack(1);
j -= wH;
teamSalary += wH;
}
} // for()
calculated = true;
} // if()
return itemList;
}

// add an item to the item list
public void add(String name, int Salary, int value)
{
if (name.Equals(""))
name = "" + (itemList.Count() + 1);
itemList.Add(new Item(name, Salary, value));
setInitialStateForCalculation();
}

// add an item to the item list
public void add(int Salary, int value)
{
add("", Salary, value); // the name will be "itemList.size() + 1"!
}

// remove an item from the item list
public void remove(String name)
{
for (int pointer = 0; pointer <= itemList.Count-1; pointer++)

{
itemList[pointer].getName().Equals("");

if (itemList.ElementAt(pointer).getName().Equals(itemList.ElementAt(pointer).getName()))
{
itemList.Remove(itemList.ElementAt(pointer));
}
}
setInitialStateForCalculation();
}

// remove all items from the item list
public void removeAllItems()
{
itemList.Clear();
setInitialStateForCalculation();
}

public int getPoints()
{
if (!calculated)
calcSolution();
return points;
}

public int getSolutionSalary() { return teamSalary; }
public bool isCalculated() { return calculated; }
public int getMaxSalary() { return maxSalary; }

public void setTeamSize(int _teamSize)
{
teamSize = _teamSize;
}

public int getTeamSize()
{
return teamSize;
}

public void setMaxSalary(int _maxSalary)
{
maxSalary = Math.Max(_maxSalary, 0);
}

public void setItemList(List<Item> _itemList) {
if (_itemList != null) {
itemList = _itemList;
foreach (Item item in _itemList) {
item.checkMembers();
}
}
}

// set the member with name "inKnapsack" by all items:
private void setInKnapsackByAll(int inKnapsack) {
foreach (Item item in itemList)
if (inKnapsack > 0)
item.setInKnapsack(1);
else
item.setInKnapsack(0);
}

// set the data members of class in the state of starting the calculation:
protected void setInitialStateForCalculation()
{
setInKnapsackByAll(0);
calculated = false;
points = 0;
teamSalary = 0;
teamSize = 0;
}

} 
}

ご協力いただきありがとうございます!

4

5 に答える 5

3

enter image description here

Plot the players on a graph, X-axis is points, Y-axis is salary, zeroes at the origin. Lower right players will be desirable cheap high scorers, upper left players will be undesirable expensive low scorers, players on the diagonal will be equivalent (same cost per point). Sweep an origin-anchored radius from the X-horizontal counter-clockwise to the Y-vertical, forming an ever-growing pie slice, until either 20 players are inside the slice or the total salaries inside the slice reaches the cap. If you reach the $ cap but not 20 players, delete the "upper leftmost" player inside the slice and continue. If you reach 20 players but not the cap, you can delete a low-scorer from the slice to make room for a higher-scoring player just about to enter the slice, making your total cost per point unnecessarily rise, but since it's funny money and you're not a real owner, go for it.

于 2011-09-17T04:09:23.093 に答える
3

残念ながら、この問題はNP 困難であるため、適切な解決策が見つかるとは期待できません。P = NPでない限り、多項式時間アルゴリズムは存在せず、網羅的検索が最良のアルゴリズムの 1 つになる可能性があります (ただし、ヒューリスティックを使用して高速化することもできます)。

この問題が NP 困難であることを確認するために、ナップザック問題を多項式時間で NP 困難にする方法を示します。集合 S = {(重み1 , 値1 ), (重み2 , 値2 ), ... , (重みn , 値n )} と重み制限 k で構成される部分集合和問題のインスタンスが与えられると、給与が体重で期待ポイントが価値である選手のセットを作成することにより、ホッケーの問題のインスタンスを構築します。次に、給与が k を超えない選手の最大体重の組み合わせを見つけようとします。これは、目標体重を超えない元のナップザック問題で作成できる最大合計と同じです。

ただし、投稿したコードが示すように、ナップザックの問題を解決するための疑似多項式時間アルゴリズムがあります。給与が低い (または給与を小さな数値に正規化できる) と仮定すると、これをうまく利用できる可能性があります。

正確な答えを得るための多項式時間アルゴリズムがあるかどうかは疑わしいですが、ほぼ最適な解でよければ、ナップザック問題の解を近似するための多項式時間アルゴリズムがあります。詳細については、2 つのアルゴリズムについて詳しく説明しているこれらのメモを確認してください。興味深いことに、それらはあなたが使用しているように見える疑似多項式時間アルゴリズムに依存しているので、おそらく実装は簡単でしょうか?

数学での素晴らしい解決策に対するあなたの希望を打ち砕いて申し訳ありません...NP困難な問題はそれをする傾向があります。:-(

于 2011-09-17T02:17:23.380 に答える
2

この種の問題に取り組む楽しい方法は、遺伝的アルゴリズムを使用することです。そして実際、私は自分のホッケープールのためにそれをやったのです!

興味があれば、ここで Scala コード全体を見ることができますが、その核心は次のとおりです。

def GA(nbIter: Int, popSize: Int, tournamentSize: Int) = {
  def loop(iter: Int, pop: Seq[LineUp]): LineUp = {
    val best = pop.maxBy(_.fitness)
    println("\nBest of generation " + iter + best)
    if (iter == nbIter)
      best
    else {
      val newPop = for {
        _ ← Stream.continually()
        x = tournament(pop, tournamentSize).head
        y = (x.mutants take 5) maxBy (_.fitness)
      } yield y
      loop(iter + 1, best +: newPop.take(popSize - 1))
    }
  }
  val initialPop = LineUp.randomStream.take(popSize)
  loop(0, initialPop)
} 

まず、有効なラインナップのランダムなコレクションを生成します (サラリー キャップとポジションの制約を考慮します)。反復ごとに、トーナメント選択ヒル クライミングの組み合わせを使用して、新しい人口を生成します。「フィットネス」は、タイ ブレーカーとして最低給与のラインナップの予想ポイント数として単純に定義されます。

もちろん、これはヒューリスティックにすぎませんが、プレーヤーのリストがそれほど大きくなく、アルゴリズムをしばらく実行する余裕がある場合は、かなり最適なソリューションを見つける可能性が高くなります。

于 2011-10-12T18:00:00.683 に答える
0

ILPとして簡単に定式化できます。それらを解くことも NP 困難ですが、問題のインスタンスはそれほど大きくないように見えるので、完全に解ける可能性があります (1 つのソルバーは lpsolve などです)。問題のサイズが原因で完全に解けない場合でも、ILP には適切なヒューリスティックが存在します。

于 2011-09-17T06:28:07.873 に答える
0

問題は難しいかもしれませんが、実行時間を改善するために、ゴールキーパーがオフェンスでプレーしないという事実を利用できます (より正式には、選択したエンティティはさまざまなカテゴリのものです)。

また、問題に対するおおよその解決策を見つけたい場合もあります。その値を使用して、最適解とその検索を制限します。

于 2011-09-17T06:09:33.720 に答える