Comment écrire 2 ** n - 1 comme fonction récursive?

49

J'ai besoin d'une fonction qui prend n et renvoie 2 n - 1 . Cela semble assez simple, mais la fonction doit être récursive. Jusqu'à présent, je n'ai que 2 n :

def required_steps(n):
    if n == 0:
        return 1
    return 2 * req_steps(n-1)

L'exercice indique: "Vous pouvez supposer que le paramètre n est toujours un entier positif et supérieur à 0"

Kajice
la source
4
Pour mémoire, il devrait être beaucoup plus efficace de le faire comme une personne normale avec un décalage et une soustraction. Les entiers Python ont une largeur arbitraire et 1 << nne peuvent donc pas déborder. Cela semble être un exercice pour inventer un moyen de se décomposer (1<<n) - 1en plusieurs étapes, en réglant peut-être chaque bit un par un comme le montrent certaines réponses.
Peter Cordes
8
def fn(n): if n == 0: return 1; return (2 << n) - fn(0); # technically recursive
MooseBoys
3
@Voo: Pas Carl, mais veuillez me lister tout ce qui est contenuC:\MyFolder
Flater
1
@Voo: La dépendance ou non n'est pas pertinente pour un exercice qui se concentre uniquement sur l'enseignement du concept de récursivité. Je pourrais créer un ensemble de classes / méthodes simulées de base que les élèves pourraient utiliser. Vous vous concentrez sur quelque chose qui dépasse complètement le point de l'exercice. L'utilisation de la navigation dans le système de fichiers est un bon exemple parce que les étudiants comprennent généralement la nature intrinsèquement récurrente des dossiers et des fichiers (c'est-à-dire que les dossiers peuvent être imbriqués les uns dans les autres presque indéfiniment)
Flater
1
@Voo Non, je dis que vous pouvez enseigner la récursivité en montrant une structure de données récursive. Je ne sais pas pourquoi vous avez du mal à comprendre cela.
Flater

Réponses:

54

2**n -1est également 1 + 2 + 4 + ... + 2 n-1 qui peut être transformé en une seule fonction récursive (sans la seconde pour soustraire 1 de la puissance de 2).

Indice : 1 + 2 * (1 + 2 * (...))

Solution ci-dessous, ne cherchez pas si vous voulez essayer l'indice en premier.


Cela fonctionne si nest garanti supérieur à zéro (comme promis dans l'énoncé du problème):

def required_steps(n):
    if n == 1: # changed because we need one less going down
        return 1
    return 1 + 2 * required_steps(n-1)

Une version plus robuste gérerait également les valeurs nulles et négatives:

def required_steps(n):
    if n < 0:
        raise ValueError("n must be non-negative")
    if n == 0:
        return 0
    return 1 + 2 * required_steps(n-1)

(L'ajout d'une vérification pour les non-entiers est laissé comme exercice.)

h4z3
la source
4
mais required_steps(0)provoque maintenant une récursion infinie
Merci
7
2^0 - 1== 0. Ajoutez un autre ifpour ce cas.
h4z3
9
@ user633183 Oui, je sais ce qu'est une fonction totale. Le faites vous? Parce que ce ne sera jamais une fonction totale. Les autres réponses ne sont pas non plus des fonctions totales. Et oui, plus de code serait nécessaire pour en faire des fonctions totales. - Comme je l'ai dit, nous n'avons pas de domaine. Que devons-nous supposer que notre domaine est? Même si c'est juste int, nous ne savons pas quoi faire lorsque n <0. Calculer? Lancer une erreur? Retour 0? Dans ce cas, nous ne pouvons faire qu'une fonction partielle (définissez-la pour les choses dont nous savons quel est le résultat).
h4z3
4
Le cas de base dans le code de l'OP est 0et utilise n - 1pour le sous-problème. Un domaine de nombres naturels semble être un bon ajustement.
Merci
4
Merci beaucoup! À mon humble avis, c'est la meilleure solution pour mon problème spécifique. Je n'ai pas indiqué de valeurs possibles pour n, vraiment désolé! Je sais que c'est un peu important ... l'exercice indique: "Vous pouvez supposer que le paramètre n est toujours un entier positif et supérieur à 0"
Kajice
37

Pour résoudre un problème avec une approche récursive, vous devez trouver comment définir la fonction avec une entrée donnée en termes de la même fonction avec une entrée différente. Dans ce cas, depuis f(n) = 2 * f(n - 1) + 1, vous pouvez faire:

def required_steps(n):
    return n and 2 * required_steps(n - 1) + 1

pour que:

for i in range(5):
    print(required_steps(i))

les sorties:

0
1
3
7
15
blhsing
la source
9

Vous pouvez extraire la partie vraiment récursive vers une autre fonction

def f(n):
    return required_steps(n) - 1

Ou vous pouvez définir un indicateur et définir à quel moment soustraire

def required_steps(n, sub=True):
    if n == 0: return 1
    return 2 * required_steps(n-1, False) - sub

>>> print(required_steps(10))
1023
rafaelc
la source
0

En utilisant un paramètre supplémentaire pour le résultat, r-

def required_steps (n = 0, r = 1):
  if n == 0:
    return r - 1
  else:
    return required_steps(n - 1, r * 2)

for x in range(6):
  print(f"f({x}) = {required_steps(x)}")

# f(0) = 0
# f(1) = 1
# f(2) = 3
# f(3) = 7
# f(4) = 15
# f(5) = 31

Vous pouvez également l'écrire en utilisant le décalage gauche au niveau du bit, <<-

def required_steps (n = 0, r = 1):
  if n == 0:
    return r - 1
  else:
    return required_steps(n - 1, r << 1)

La sortie est la même

Je vous remercie
la source
2
Inutile d'impliquer des opérations au niveau du bit pour un simple exercice de multiplication .. pas du tout lisible. En outre, pas besoin de la elseclause dans l'une ou l'autre fonction
rafaelc
La seule différence est en train de changer r * 2pour r << 1et qui est « pas lisible du tout »? 😂
Merci
2
Inventer un 2ème paramètre se juste cela en une boucle qui décale vers la gauche nfois et puis 1. Semble soustraient encore moins élégante alors nécessaire, même si la chose est un exercice de l' inefficacité par rapport (1<<n) - 1.
Peter Cordes
1
@PeterCordes: Le déplacement de l'état dans un paramètre d'accumulateur est le moyen standard de transformer un appel récursif en un appel récursif de queue. Maintenant, malheureusement, Python ne prend pas en charge les appels de queue appropriés, pas même la récursion de queue appropriée, mais cela ne signifie pas que ce n'est pas une technique utile à apprendre pour que vous puissiez l'appliquer dans d'autres langues qui mettent en œuvre les appels de queue appropriés. ou au moins une récursion de queue appropriée.
Jörg W Mittag
1
@ JörgWMittag Oui, mais dans ce cas, il est difficile de dissimuler le fait que ce serait plus naturel en boucle. C'est peut-être juste que je passe autant de temps sur le langage d'assemblage et les performances, mais écrire une "boucle" en utilisant la récursion de queue semble inutile dans un langage impératif quand vous pouvez simplement écrire une boucle. Ou peut-être que ce qui me dérange dans cette réponse, c'est juste le choix de la façon de se décomposer: en décalages un par un, puis une soustraction finale comme cas de base. Probablement une combinaison des deux.
Peter Cordes
0

Avoir un espace réservé pour se souvenir de la valeur d'origine de n, puis pour la toute première étape, c'est n == N-à- dire retourner2^n-1

n = 10
# constant to hold initial value of n
N = n
def required_steps(n, N):
    if n == 0:
        return 1
    elif n == N:
        return 2 * required_steps(n-1, N) - 1
    return 2 * required_steps(n-1, N)

required_steps(n, N)
eMad
la source
-1

Une façon d'obtenir l'offset de "-1" consiste à l'appliquer dans le retour du premier appel de fonction à l'aide d'un argument avec une valeur par défaut, puis de définir explicitement l'argument offset à zéro lors des appels récursifs.

def required_steps(n, offset = -1):
    if n == 0:
        return 1
    return offset + 2 * required_steps(n-1,0)
Eric Towers
la source
-1

En plus de toutes les réponses impressionnantes données précédemment, ci-dessous montrera son implémentation avec les fonctions internes.

def outer(n):
    k=n
    def p(n):
        if n==1:
            return 2
        if n==k:
            return 2*p(n-1)-1
        return 2*p(n-1)
    return p(n)

n=5
print(outer(n))

Fondamentalement, il attribue une valeur globale de n à k et revient à travers elle avec des comparaisons appropriées.

Vous roulez
la source