web-dev-qa-db-fra.com

Bases de la récursivité en Python

"Écrire une fonction récursive," listSum "qui prend une liste d’entiers et renvoie la somme de tous les entiers de la liste". 

Exemple:

>>>> listSum([1,3,4,5,6])
19

Je sais comment procéder autrement, mais pas de manière récursive.

def listSum(ls):
    i = 0
    s = 0
    while i < len(ls):
        s = s + ls[i]
        i = i + 1
    print s

J'ai besoin de la méthode de base pour le faire car les fonctions intégrées spéciales ne sont pas autorisées.

22
Sebastian S

Lorsque vous rencontrez un problème de ce type, essayez d’exprimer le résultat de la fonction avec la même fonction.

Dans votre cas, vous pouvez obtenir le résultat en ajoutant le premier nombre et en appelant la même fonction avec le reste des éléments de la liste.

Par exemple,

listSum([1, 3, 4, 5, 6]) = 1 + listSum([3, 4, 5, 6])
                         = 1 + (3 + listSum([4, 5, 6]))
                         = 1 + (3 + (4 + listSum([5, 6])))
                         = 1 + (3 + (4 + (5 + listSum([6]))))
                         = 1 + (3 + (4 + (5 + (6 + listSum([])))))

Maintenant, quel devrait être le résultat de listSum([])? Il devrait être 0. Cela s'appelle condition de base de votre récursion. Lorsque la condition de base est remplie, la récursivité prend fin. Maintenant, essayons de l'implémenter.

L'essentiel ici est de scinder la liste. Vous pouvez utiliser slicing pour le faire.

Version simple

>>> def listSum(ls):
...     # Base condition
...     if not ls:
...         return 0
...
...     # First element + result of calling `listsum` with rest of the elements
...     return ls[0] + listSum(ls[1:])
>>> 
>>> listSum([1, 3, 4, 5, 6])
19

Rappel d'appel final

Une fois que vous avez compris comment fonctionne la récursivité ci-dessus, vous pouvez essayer de l'améliorer un peu. Maintenant, pour trouver le résultat réel, nous dépendons également de la valeur de la fonction précédente. L'instruction return ne peut pas renvoyer immédiatement la valeur tant que l'appel récursif n'a pas renvoyé de résultat. Nous pouvons éviter cela en passant le courant au paramètre de fonction, comme ceci

>>> def listSum(ls, result):
...     if not ls:
...         return result
...     return listSum(ls[1:], result + ls[0])
... 
>>> listSum([1, 3, 4, 5, 6], 0)
19

Ici, nous passons la valeur initiale de la somme dans les paramètres, qui est zéro dans listSum([1, 3, 4, 5, 6], 0). Ensuite, lorsque la condition de base est remplie, nous accumulons la somme dans le paramètre result. Nous la retournons donc. Maintenant, la dernière instruction return a listSum(ls[1:], result + ls[0]), où nous ajoutons le premier élément à la result actuelle et le passons à nouveau à l'appel récursif.

Cela pourrait être un bon moment pour comprendre Tail Call . Cela ne serait pas pertinent pour Python, car cela n’optimise pas l’appel Tail.


En passant par la version d'index

Maintenant, vous pourriez penser que nous créons autant de listes intermédiaires. Puis-je éviter cela?

Bien sûr vous pouvez. Vous avez juste besoin de l’index de l’élément à traiter ensuite. Mais maintenant, la condition de base sera différente. Puisque nous allons passer un index, comment allons-nous déterminer comment la liste entière a été traitée? Eh bien, si l'index est égal à la longueur de la liste, nous en avons traité tous les éléments.

>>> def listSum(ls, index, result):
...     # Base condition
...     if index == len(ls):
...         return result
...
...     # Call with next index and add the current element to result
...     return listSum(ls, index + 1, result + ls[index])
... 
>>> listSum([1, 3, 4, 5, 6], 0, 0)
19

Version de la fonction interne

Si vous regardez maintenant la définition de la fonction, vous lui passez trois paramètres. Disons que vous allez publier cette fonction en tant qu'API. Sera-t-il commode pour les utilisateurs de passer trois valeurs, alors qu’ils trouvent la somme d’une liste?

Nan. Que pouvons-nous y faire? Nous pouvons créer une autre fonction, qui est locale à la fonction listSum réelle, et nous pouvons lui transmettre tous les paramètres liés à l'implémentation, comme ceci

>>> def listSum(ls):
...
...     def recursion(index, result):
...         if index == len(ls):
...             return result
...         return recursion(index + 1, result + ls[index])
...
...     return recursion(0, 0)
... 
>>> listSum([1, 3, 4, 5, 6])
19

Désormais, lorsque la variable listSum est appelée, elle renvoie simplement la valeur de retour de la fonction interne recursion, qui accepte les paramètres index et result. Nous ne faisons que transmettre ces valeurs, pas les utilisateurs de listSum. Ils doivent juste passer la liste pour être traités.

Dans ce cas, si vous observez les paramètres, nous ne passons pas de ls à recursion mais nous l'utilisons à l'intérieur. ls est accessible dans recursion en raison de la propriété de fermeture.


Version des paramètres par défaut

Maintenant, si vous voulez garder les choses simples, sans créer de fonction interne, vous pouvez utiliser les paramètres par défaut, comme ceci

>>> def listSum(ls, index=0, result=0):
...     # Base condition
...     if index == len(ls):
...         return result
...
...     # Call with next index and add the current element to result
...     return listSum(ls, index + 1, result + ls[index])
... 
>>> listSum([1, 3, 4, 5, 6])
19

Désormais, si l'appelant ne transmet pas explicitement de valeur, 0 sera attribué à la fois à index et à result.


Problème de puissance récursive

Maintenant, appliquons les idées à un problème différent. Par exemple, essayons d'implémenter la fonction power(base, exponent). Il renverrait la valeur de base élevé à la puissance exponent.

power(2, 5) = 32
power(5, 2) = 25
power(3, 4) = 81

Maintenant, comment pouvons-nous faire cela récursivement? Essayons de comprendre comment ces résultats sont obtenus.

power(2, 5) = 2 * 2 * 2 * 2 * 2 = 32
power(5, 2) = 5 * 5             = 25
power(3, 4) = 3 * 3 * 3 * 3     = 81

Hmmm, alors on a eu l'idée. La base multipliée par elle-même, exponent fois donne le résultat. Ok, comment on s'en approche. Essayons de définir la solution avec la même fonction.

power(2, 5) = 2 * power(2, 4)
            = 2 * (2 * power(2, 3))
            = 2 * (2 * (2 * power(2, 2)))
            = 2 * (2 * (2 * (2 * power(2, 1))))

Quel devrait être le résultat si quelque chose était élevé au pouvoir 1? Le résultat sera le même nombre, non? Nous avons eu notre condition de base pour notre récursion :-)

            = 2 * (2 * (2 * (2 * 2)))
            = 2 * (2 * (2 * 4))
            = 2 * (2 * 8)
            = 2 * 16
            = 32

Très bien, permet de le mettre en œuvre.

>>> def power(base, exponent):
...     # Base condition, if `exponent` is lesser than or equal to 1, return `base`
...     if exponent <= 1:
...         return base
...
...     return base * power(base, exponent - 1)
... 
>>> power(2, 5)
32
>>> power(5, 2)
25
>>> power(3, 4)
81

OK, comment va-t-on définir la version optimisée de l'appel final? Permet de transmettre le résultat actuel en tant que paramètre à la fonction elle-même et de renvoyer le résultat lorsque la condition de base remplie. Restons simples et utilisons directement l'approche du paramètre par défaut.

>>> def power(base, exponent, result=1):
...     # Since we start with `1`, base condition would be exponent reaching 0
...     if exponent <= 0:
...         return result
...
...     return power(base, exponent - 1, result * base)
... 
>>> power(2, 5)
32
>>> power(5, 2)
25
>>> power(3, 4)
81

Maintenant, nous réduisons la valeur exponent dans chaque appel récursif et plusieurs result avec base et le transmettons à l'appel récursif power. Nous commençons avec la valeur 1, car nous abordons le problème en sens inverse. La récursion va se passer comme ça

power(2, 5, 1) = power(2, 4, 1 * 2)
               = power(2, 4, 2)
               = power(2, 3, 2 * 2)
               = power(2, 3, 4)
               = power(2, 2, 4 * 2)
               = power(2, 2, 8)
               = power(2, 1, 8 * 2)
               = power(2, 1, 16)
               = power(2, 0, 16 * 2)
               = power(2, 0, 32)

Puisque exponent devient zéro, la condition de base est remplie et la result sera renvoyée. Nous obtenons donc 32 :-)

78
thefourtheye

La sortie précoce est typique des fonctions récursives. seq est faux quand vide (donc quand il n'y a plus de nombres à additionner).

La syntaxe Slice permet de transmettre une séquence à une fonction appelée récursivement sans qu'un entier ne soit utilisé à l'étape en cours.

def listSum(seq):
    if not seq:
        return 0
    return seq[0] + listSum(seq[1:])

print listSum([1,3,4,5,6])  # prints 19
3
Łukasz Rogalski
def power(a,b): #a^b
    if b==0:
        return 1
    Elif b>0:
        return a * power(a,b-1)
    Elif b<0:
        return power(a, b+1)/a
0
Anonymous
def listsum(list):
    if len(list) == 1:
        return list[0]
    else:
        return list[0] + listsum(list[1:])

print(listsum([1,5,9,10,20]))

L'idée de base de cette fonction récursive est que nous voulons vérifier si nous avons un cas de base qui est affiché comme if len(list) == 1:. Pour le cas de base, nous renvoyons simplement la valeur dans la liste return list[0], sinon nous avons toujours plusieurs éléments dans la liste. Dans l'instruction else:, nous allons ajouter le premier élément de la liste, qui est list[0], au reste des éléments de la liste. Ceci est montré en appelant la fonction de manière récursive avec la liste plus courte d'un élément - l'élément situé à l'index 0 - listsum(list[1:]), ce processus est répété avec la liste qui devient plus petite jusqu'à arriver au cas de base - une liste de longueur 1 et vous obtiendrez un résultat final.

0
RamJet
def listSum(L):
    """Returns a sum of integers for a list containing
    integers.
    input: list of integers
    output: listSum returns a sum of all the integers
    in L.
    """
    if L == []:
        return []
    if len(L) == 1:
        return L[0]
    else:
        return L[0] + listSum(L[1:])
print listSum([1, 3, 4, 5, 6])
print listSum([])
print listSum([8])
0
Eliza

Une autre version:

def listSum(ls):
    ls_len = len(ls)

    # Base condition
    if ls_len==1:
        return ls[0]
    if ls_len==0:
        return None
    # ls = listSum(ls[0:i]) + listSum(ls[i:])
    Elif ls_len%2==0:
            i = int(ls_len/2)
            return listSum(ls[0:i]) + listSum(ls[i:])
    else:
        i = int((ls_len-1)/2)
        return listSum(ls[0:i]) + listSum(ls[i:])

Suivez l'exemple de @ thefourtheye, nous pouvons dire:

listSum([1, 3, 4, 5, 6]) = listSum([1, 3]) + listSum([4, 5, 6])
                         = (listSum([1]) + listSum([3])) + (listSum([4]) + listSum([5, 6]))
                         = (listSum([1]) + listSum([3])) + (listSum([4]) + (listSum([5]) + listSum([6])))

Condition de base: lorsque ls n'a qu'un seul élément, renvoyez cette valeur.

0
Belter