Sto provando a scrivere una funzione che trova tutte le combinazioni possibili di monete che producono una quantità specificata, ad esempio calcola tutto il possibile modo di dare un cambio per la somma di 2 sterline inglesi l'elenco delle denominazioni 1p, 2p, 5p, 10p, 20p, 50p, 1pound, 2pound. Sono bloccato con questo e non riesco a trovare la soluzione giusta.La funzione di Baktracking che calcola la modifica supera la profondità massima di ricorsione
Voglio che la funzione principale sia ricorsiva, perché voglio capire meglio la ricorsione. L'algoritmo deve tornare indietro, se la combinazione trovata in un momento supera l'importo che deve essere abbinato, il programma dovrebbe tornare ai passaggi precedenti e partire da un punto diverso.
Finora ho scritto una funzione normale (non ricorsiva) che calcola tutte le combinazioni possibili di monete in un dato paese se ogni moneta viene utilizzata una sola volta (questo è abbastanza semplice). Non sto ancora cercando la combinazione giusta per una data somma, solo tutte le possibili combinazioni di monete.
def calcCoins(coins):
"""
returns all possible combinations of coins, when called with
[1,2,5,10,20,50,100,200] returns a list of 126 Counters containing
for instance Counter{1:1}, Counter{1:1,2:1,5:1}, Counter {50:1,100:1} etc
"""
i,combs = 1, []
while i < len(coins):
for x in combinations(coins,i):
combs.append(Counter(x))
i += 1
return combs
Ora ho una funzione ricorsiva goffo che accetta una combinazione e la quantità desiderata come argomenti e restituisce tutti i possibili modi in cui può essere dato un cambio pari a tale importo.
def findSum(comb,goal,rightOnes):
if rightOnes == None:
rightOnes = []
if sum(comb.elements()) == goal:
comb_ = Counter(comb)
if comb_ in rightOnes:
# probably a cycle, return combinations gathered and exit
return rightOnes
rightOnes.append(comb_)
elif sum(comb.elements()) > goal:
#this is meant to be backtracking
return False
for k in comb:
comb[k] += 1
if findSum(comb,goal,rightOnes) != False:
return findSum(comb,goal,rightOnes)
else:
comb[k] = 1
return rightOnes
Le piste di funzione e restituisce in modo corretto per molto piccole combinazioni: per esempio per
test2 = Counter({10: 1, 20: 1})
findSum(test2,200,[])
Restituisce:
[Counter({10: 18, 20: 1}), Counter({10: 16, 20: 2}),
Counter({10: 14, 20: 3}), Counter({10: 12, 20: 4}),
Counter({10: 10, 20: 5}), Counter({10: 8, 20: 6}),
Counter({20: 7, 10: 6}), Counter({20: 8, 10: 4}),
Counter({20: 9, 10: 2})]
Ma per quelli più grandi, come ad esempio
test3 = Counter({1: 1, 2: 1, 10: 1})
test4 = Counter({1: 1, 2: 1, 100: 1, 10: 1})
si supera il limite di ricorsione. Funziona bene fino a un certo momento, stampa risultati parziali ma poi ad un certo punto supera la massima profondità di ricorsione.
Quali sono gli errori che sto facendo e perché questa funzione si esegue in modo incontrollato? È qualcosa con la mia implementazione del backtracking? Sto omettendo un caso? Come ottimizzare questa funzione in modo che non superi la massima profondità di ricorsione?
Grazie in anticipo!
EDIT: Ecco traceback:
if findSum(comb,goal,rightOnes) != False:
File "C:\playground\python\problem31.py", line 105, in findSum
if sum(comb.elements()) == goal:
File "C:\Python27\lib\collections.py", line 459, in elements
return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
RuntimeError: maximum recursion depth exceeded while calling a Python object
e l'ultimo risultato parziale, poco prima della pausa della funzione (chiamata con test3)
[Counter({1: 163, 2: 1, 20: 1, 10: 1, 5: 1}), Counter({1: 161, 2: 2, 20: 1, 10: 1, 5: 1}),
Counter({1: 159, 2: 3, 20: 1, 10: 1, 5: 1}), Counter({1: 157, 2: 4, 20: 1, 10: 1, 5: 1}),
Counter({1: 155, 2: 5, 20: 1, 10: 1, 5: 1}), Counter({1: 153, 2: 6, 20: 1, 10: 1, 5: 1})]
la funzione presuppone che tutte e almeno una volta le monete nella combinazione iniziale verranno utilizzate. in 'test3' e' test4' quale obiettivo hai definito? la funzione che restituisce un risultato parziale significa che ha colpito il risultato finale. Possiamo ottenere anche il backtrace e il risultato parziale, perché ho il sospetto che sia la chiamata a 'Counter''s' __repr__' che sta causando la grande ricorsione. –
L'obiettivo era 200, li chiamo tutti con lo stesso obiettivo. Aggiunto il traceback, punta chiaramente su Counter, perché succede quando le funzioni cercano di sommare gli elementi del contatore. –
Odio dirlo, ma Python non è la lingua migliore per imparare una programmazione così ricorsiva. Vedi, per esempio, [questa risposta] (http://stackoverflow.com/a/2401520/626998). –