[Python] Itertools x sport
Daniele Varrazzo
piro a develer.com
Mer 16 Gen 2013 15:46:46 CET
On 2013-01-16 11:00, Gianni wrote:
> Buongiorno, sto cercando la via più pratica per popolare il database
> di uno script per la gestione di campionati di calcio*.
>
> Dovrei associare le possibili combinazioni di partite (380) alle
> giornate (38) facendo in modo che ogni squadra giochi una sola
> partita
> per giornata. Quindi, ho 2 liste: 20 squadre e 38 giornate.
> Le 380 partite le ottengo con permutations:
> partite = itertools.permutations(squadre, 2)
>
> Fin qui ci arrivo ma vado in palla quando cerco di associare le
> partite alle giornate.
Veramente questo mi sembra abbia un problema: non hai una distinzione
netta tra andata e ritorno. Ovvero puoi giocare S1-S2 un giorno, poi
S2-S1 il giorno successivo, mentre dovresti giocare S2-S1 dopo che S1 ha
giocato con tutti gli altri. Quindi dividerei il campionato in 2 parti
nette, andata e ritorno, e risolverei due problemi distinti: uno con
[ p for p in itertools.permutations(range(nteams),2) if p[0] < p[1]
]
e l'altro a squadre invertite (ma poi gestendo le coppie in maniera da
non far giocare la prima squadra sempre in casa e via dicendo).
Un modo di fare tutto usando itertools non mi viene in mente.
Ovviamente si tratta di un sottoinsieme di un prodotto di qualche
insieme, ma è troppo generica come soluzione.
Ho provato un algoritmo semplice: iterare sulle partite di squadre e
assegnare una partita ad ogni giornata dove quella partita fosse
possibile, ovvero le due squadre non erano già impegnate: in pratica
(esempio con 6 squadre):
subs = []
for p in list(itertools.permutations(range(6),2)):
if p[0] < p[1]:
continue
for s in subs:
if p[0] not in s[0] and p[1] not in s[0]:
s[0].add(p[0])
s[0].add(p[1])
s[1].append(p)
break
else:
subs.append((set(p),[p]))
print [s[1] for s in subs]
Ma... non funziona. Mi aspetto che il risultato siano 5 liste di 3
elementi ciascuna, invece è:
[[(1, 0), (3, 2), (5, 4)],
[(2, 0), (3, 1)],
[(2, 1), (3, 0)],
[(4, 0), (5, 1)],
[(4, 1), (5, 0)],
[(4, 2), (5, 3)],
[(4, 3), (5, 2)]]
Il problema è che se giocano 1-0 e 3-2 il primo giorno, poi 2-0 e 3-1
il secondo, 5-4 dovrebbero giocare due volte.
A questo punto proverei a compilare esplicitamente n-1 giornate,
mettendo n/2 partite in ognuna, usando il backtrack ogni volta che una
giornata è in condizione impossibile (implementato con una funzione
ricorsiva). Ogni volta che una giornata è completata, le partite vengono
eliminate dal pool delle partite possibili e la giornata successiva
viene cercata in un numero ridotto di partite. Risultato:
import itertools
nteams = 6
pairs = [ p for p in itertools.permutations(range(nteams),2) if
p[0] < p[1] ]
class Found(Exception):
pass
def make_matches(matches, start):
for i, p in enumerate(pairs[start:]):
if p[0] not in matches and p[1] not in matches:
nmatches = matches + p
if len(nmatches) == nteams:
raise Found(nmatches)
else:
make_matches(nmatches, i+1)
calendar = []
while pairs:
try:
make_matches((), 0)
except Found, sol:
sol = sol.args[0]
matches = [ sol[i:i+2] for i in range(0, nteams, 2) ]
calendar.append(matches)
for p in matches:
pairs.remove(p)
else:
raise Exception('solution not found')
import pprint
pprint.pprint(calendar)
Questo stampa un calendario molto più verosimile.
[[(0, 1), (2, 3), (4, 5)],
[(0, 2), (1, 4), (3, 5)],
[(0, 3), (1, 5), (2, 4)],
[(0, 4), (1, 3), (2, 5)],
[(0, 5), (1, 2), (3, 4)]]
Ovviamente l'algoritmo non si preoccupa di far sì che ogni squadra
giochi tante volte in casa quante fuori, preferibilmente in maniera
alternata (non so se si fa così nel campionato vero): potrebbe essere
qualcosa che si può fare come preprocessing della lista di partite, o
postprocessing della soluzione trovata, o potrebbe essere necessario
modificare la ricerca per includere condizioni aggiuntive.
Penso sia anche abbastanza facile passare da una funzione che
restituisce la prima soluzione ad un generatore che restituisce tutte le
soluzioni possibili.
--
Daniele Varrazzo - Develer S.r.l.
http://www.develer.com
Maggiori informazioni sulla lista
Python