2017-09-29 5 views
-1

Le problème est le plus populaire pour illustrer la programmation dynamique, qui est la suivante. Nous avons des pièces de monnaie illimitées de chacune des dénominations 1, 3 et 5. Nous voulons que le nombre minimum de pièces pour obtenir le montant N.Nombre minimum de pièces pour constituer un montant en utilisant la récursivité

Je suis conscient de la méthode de programmation dynamique où nous construisons une solution à partir de la base cas (s). Mais je voulais voir comment écrire une solution purement récursive. Je suis capable de travailler facilement à la main pour N = 11 et dénominations = [1,3,5]. Mais pour une raison quelconque, je suis incapable de faire le travail suivant.

def minNumberOfCoins(amount, denominations): 
     if amount <= 0: 
      return(0) 
     if amount in denominations: 
      return(1) 
     else: 
      listToExamine = list(filter(lambda x: x > 0, map(lambda x: amount - x, denominations))) 
      print(listToExamine) 
      minVal = min(listToExamine, key=lambda x: 1 + minNumberOfCoins(x, denominations)) 
      return(minVal) 

Pour autant que je sache, la logique est identique à ce que j'ai élaboré sur papier. Y a-t-il une nuance dans la récursivité de Python que je ne connais pas, ou y a-t-il quelque chose de subtil qui me manque? Je vous remercie!

+1

Bien que cela ne soit pas pertinent pour votre question, Python idiomatique utilise 'snake_case' par opposition à' camelCase'. – Hindol

+0

Notez que votre solution ne fonctionne pas: 'minNumberOfCoins (10, [1, 5, 6])' renvoie 5 au lieu de 2. Elle soulève également une exception sur 'minNumberOfCoins (1, [2])'. – bfontaine

+0

bfontaine, oui, bien sûr, j'ai remarqué que ça ne marchait pas parce que je l'ai d'abord travaillé sur papier. Je me demandais ce que j'ai manqué en écrivant le code. Je vais passer par John Gordon pour comprendre ce que j'ai manqué. – user4562262

Répondre

0

Cette mise en œuvre semble plus simple:

def minNumberOfCoins(amount, denominations): 

    if amount <= 0: 
     return(0) 

    if amount in denominations: 
     return(1) 

    for d in sorted(denominations, reverse=True): 
     if d <= amount: 
      return 1 + minNumberOfCoins(amount - d, denominations) 
+0

Notez que cela retournera 'None' s'il est appelé' minNumberOfCoins (1, [2]) '. Il retournera aussi '5' au lieu de' 2' s'il est appelé 'minNumberOfCoins (10, [1, 5, 6])' (6 + 1 + 1 + 1 + 1 au lieu de 5 + 5). Edit: le code de OP déclenchera une exception sur le premier cas et échouera également sur le second cas. – bfontaine

+0

Ooo, bon point sur le cas 5 + 5. Je n'avais pas pensé à ça! –

+0

Quel devrait être le résultat de '(1, [2])'? –

0

Une approche plus lisible. Vous ne devriez pas mélanger map ou filter avec beaucoup de lambdas. Les compréhensions conditionnelles et les générateurs sont généralement les meilleurs choix:

def min_coins(amount, denominations): 
    # these two base cases seem to cover more edge cases correctly 
    if amount < 0: 
     return None 
    if amount == 0: 
     return 0 
    tries = (min_coins(amount-d, denominations) for d in denominations) 
    try: 
     return 1 + min(t for t in tries if t is not None) 
    except ValueError: # the generator in min produces no values 
     return None 

min_coins(11, [1,3,5]) 
# 3 
+0

Vous pouvez ajouter une légère optimisation en utilisant une boucle 'for' et la casser une fois que l'une des tentatives retourne' 0'. Cela vous permettrait également de supprimer le 'try' /' sauf'. – bfontaine

+1

@bfontaine Oui, j'ai aussi pensé à ça. Mais je voulais le garder aussi lisible que possible. Si vous optez pour la performance, vous choisirez un algorithme différent pour commencer. – schwobaseggl

+0

Vous avez absolument raison. – bfontaine