2011-09-17 19 views
8

Questo è un piccolo progetto divertente che ho iniziato a provare e massimizzare le mie possibilità di vincere il nostro pool di hockey in ufficio. Sto cercando di trovare il modo migliore per selezionare 20 giocatori che mi daranno il maggior numero di punti in un tetto massimo di stipendio.Algoritmo per piscina da hockey

Ad esempio, immaginiamo che i dati grezzi è fatta di

  1. il nome del giocatore
  2. posizione (avanti, Difensori, Portiere)
  3. prevista quantità di punti per questa stagione
  4. Stipendio.

Ora voglio i 20 giocatori che mi daranno il maggior numero di punti entro lo stipendio X cap. Più avanti, come fase 2, vorrei fare la stessa cosa però in questo caso, voglio solo 12 attaccanti, 6 difensori e 2 portiere.

Ora, il modo più ovvio è di fare semplicemente tutte le combinazioni possibili, tuttavia mentre questo funzionerà non è un'opzione valida come con 500 giocatori, questo avrebbe troppe combinazioni possibili. Potrei aggiungere alcuni filtri intelligenti per ridurre i 500 giocatori ai primi 50 attaccanti, i primi 30 difensori e i primi 15 portieri, ma comunque, sarebbe un processo molto lento.

Mi chiedo se ci sono altri algoritmi là fuori per implementare questo. Questo è solo per divertimento e non è una richiesta commerciale importante. Ma se hai qualche idea su come procedere, faccelo sapere.

Il mio primo tentativo è stato l'utilizzo dell'algoritmo dello zaino con l'aiuto di altre fonti. Sembra che funzioni solo con lo stipendio come parametro. Sto cercando di capire come aggiungere il parametro del team di 20 giocatori. È in. Net ma dovrebbe essere facile da convertire in Java.

Stavo pensando di fare un ciclo separato per capire le migliori squadre con 20 giocatori senza regoaglia di stipendio e quindi confrontare i due elenchi finché non trovo la squadra più alta nella lista due. Non sono sicuro.

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; 
} 

} 
} 

Grazie per il vostro aiuto!

+0

Per la fase 1 non ti interessa affatto le posizioni? Ad esempio, non vuoi almeno un portiere? –

risposta

3

Sfortunatamente, non dovresti aspettarti di trovare una buona soluzione a questo problema poiché è NP-hard. A meno che non sia P = NP, non ci sono algoritmi polinomiali, e la ricerca esaustiva è probabilmente uno dei migliori algoritmi (anche se potresti usare delle euristiche per accelerarlo).

Per vedere che questo problema è NP-rigido, verrà mostrato come ridurre knapsack problem in tempo polinomiale. Dato un esempio del problema sottoinsieme somma costituito da un insieme S = {(peso , valore), (peso , valore), ..., (peso n, valore n)} e limite di peso k, possiamo costruire un'istanza del tuo problema di hockey creando un gruppo di giocatori il cui stipendio è il peso e di cui il valore atteso è il valore atteso. Proviamo quindi a trovare la combinazione di pesi massimi di giocatori il cui stipendio non superi k, che è quindi identico alla somma massima che puoi ottenere nel problema dello zaino originale che non supera il peso target.

Come il codice che hai pubblicato mostra, tuttavia, esiste un algoritmo temporale pseudopolinomiale per risolvere il problema dello zaino.Supponendo che gli stipendi siano bassi (o che puoi normalizzarli a numeri piccoli), potresti essere in grado di usarli con buoni risultati.

Mentre è dubbio che ci sia un algoritmo tempo-polinomiale per ottenere la risposta esatta, se stai bene con una soluzione approssimativamente ottimale, c'è un algoritmo tempo-polinomiale per l'approssimazione delle soluzioni al problema dello zaino. Per i dettagli, controlla these notes, che dettaglia due algoritmi. È interessante notare che si basano sull'algoritmo temporale pseudopolinomiale che sembra utilizzare, quindi forse saranno facili da implementare?

Mi spiace buttare le tue speranze in una soluzione piacevole con la matematica ... i problemi con NP-tendono a farlo. :-(

+0

Penso che l'oscurità sia qui fuori luogo. Il collo di bottiglia nella qualità della soluzione sarà sicuramente nella qualità delle previsioni piuttosto che nel tempo/errore necessario per ottimizzare il team in base alle previsioni.Considera che dubito davvero che otterrà 3 cifre di accuratezza nelle previsioni, e quindi se discretizza il problema a 4 cifre sig, può approssimarlo nel tempo 10^significato * num giocatori in lega ~ = 10^4 * 500 = 5 milioni di calcoli. –

+0

Potresti spiegare, se hai tempo e interesse, perché la soluzione di @ mike non funziona. Se dovesse funzionare, questo non sarebbe NP-difficile, o sarebbe? – Unapiedra

+0

Sicuro! L'algoritmo che ha descritto è un algoritmo avido che estrarrà sempre il giocatore più costoso per dollaro durante lo sweep. Tuttavia, puoi costruire esempi in cui questo giocatore deve essere nella soluzione ottimale, e portarlo fuori ti lascia sempre in una situazione peggiore. Prova a pensare a un caso in cui questo sarebbe vero. – templatetypedef

3

enter image description here

trama i giocatori su un grafico, asse X è punti, asse Y è stipendio, zeri nell'origine. Giocatori Inferiore destra saranno desiderabili marcatori elevati economici, lettori superiori di sinistra sarà non sono desiderabili costosi segnapunti, i giocatori sulla diagonale saranno equivalenti (stesso costo per punto). Spazzare un raggio ancorato dall'orizzontale dall'asse X orizzontale in senso antiorario alla verticale Y, formando una fetta di torta sempre crescente, fino a quando 20 giocatori sono all'interno della fetta o gli stipendi totali all'interno della fetta raggiungono il limite.Se raggiungi il limite massimo di 20 giocatori, elimina il giocatore "in alto a sinistra" e prosegui.Se raggiungi 20 giocatori ma non il limite , puoi eliminare un punteggio basso dalla sezione per fare spazio a un punteggio più alto giocatore che sta per entrare nella fetta, aumentando inutilmente il costo totale per punto, ma dal momento che è denaro divertente e non sei un vero proprietario, fallo.

+0

Questo è un buon algoritmo avido, ma non c'è modo di garantire che fornisca una soluzione ottimale (a meno che tu non abbia appena trovato un algoritmo per dimostrare che P = NP!) – templatetypedef

+0

Non c'è stato alcun reclamo è ottimale, è solo un modo semplice per attaccare il problema con un'alta probabilità di essere altrettanto buono di qualsiasi altro metodo. (Anche una soluzione ottimale per la raccolta non garantisce che vincerà la piscina, poiché le performance passate dei giocatori non garantiranno il loro futuro successo.) Nota che, ad eccezione del primo giocatore o due afferrati dallo sweep, questo metodo si trasforma rapidamente in semplicemente scegliendo in ordine di costo più basso per punto, che è un modo ancora più semplice per selezionare ed è probabilmente buono come qualsiasi altro. –

0

Il problema potrebbe essere difficile, ma puoi usare il fatto che il tuo portiere non giocherà in attacco (più formale: le entità che selezioni sono di categorie diverse) per migliorare il tuo runtime.

Inoltre, è possibile trovare una soluzione approssimativa al problema. Usa quel valore per legare la soluzione ottimale e cercarla.

0

È possibile formularlo facilmente come ILP. Risolviarli è anche NP-Hard, ma l'istanza del problema non sembra così grande, quindi può essere risolvibile in modo perfetto (un solver è ad esempio lpsolve). Anche se non è risolvibile perfetto a causa delle dimensioni del problema, esiste una buona euristica per ILP.

2

Un modo divertente per affrontare questo tipo di problema è utilizzare uno genetic algorithm. E in realtà, l'ho fatto solo per il mio pool di hockey!

Si può vedere the whole Scala code here se siete curiosi, ma il cuore di essa è:

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) 
} 

Si inizia generando una raccolta casuale di formazioni validi (nel rispetto del tetto salariale e vincoli di posizione). Per ogni iterazione, genera quindi una nuova popolazione utilizzando una combinazione di tournament selection e hill climbing. "Fitness" è semplicemente definito come il numero atteso di punti per una formazione con lo stipendio più basso come un tie breaker.

È solo un euristico, ovviamente, ma se la tua lista di giocatori non è troppo grande e puoi permetterti di far funzionare l'algoritmo per un po ', ci sono buone probabilità che troverai una soluzione abbastanza ottimale.