Wie schreibe ich 2 ** n - 1 als rekursive Funktion?

49

Ich brauche eine Funktion, die n nimmt und 2 n - 1 zurückgibt . Es klingt einfach genug, aber die Funktion muss rekursiv sein. Bisher habe ich nur 2 n :

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

In der Übung heißt es: "Sie können davon ausgehen, dass der Parameter n immer eine positive ganze Zahl und größer als 0 ist."

Kajice
quelle
4
Nur für die Aufzeichnung sollte es wesentlich effizienter sein, es wie eine normale Person mit einer Verschiebung und Subtraktion zu tun. Python-Ganzzahlen haben eine beliebige Breite und 1 << nkönnen daher nicht überlaufen. Dies scheint eine Übung zu sein, um einen Weg zu finden, sich (1<<n) - 1in mehrere Schritte zu zerlegen , wobei möglicherweise jedes Bit einzeln gesetzt wird, wie einige Antworten zeigen.
Peter Cordes
8
def fn(n): if n == 0: return 1; return (2 << n) - fn(0); # technically recursive
MooseBoys
3
@ Voo: Nicht Carl, aber bitte listen Sie mir alles auf, was inC:\MyFolder
Flater
1
@Voo: Abhängigkeit oder nicht ist für eine Übung irrelevant, die sich ausschließlich darauf konzentriert, das Konzept der Rekursion zu lehren. Ich könnte eine grundlegende verspottete Reihe von Klassen / Methoden erstellen, die die Schüler verwenden könnten. Sie konzentrieren sich auf etwas, das völlig außerhalb des Übungspunktes liegt. Die Verwendung der Dateisystemnavigation ist ein gutes Beispiel, da die Schüler im Allgemeinen die inhärent wiederkehrende Natur von Ordnern und Dateien verstehen (dh Ordner können nahezu unbegrenzt ineinander verschachtelt sein)
Flater
1
@Voo Nein, ich sage, dass Sie Rekursion lehren können, indem Sie eine rekursive Datenstruktur anzeigen. Ich habe keine Ahnung, warum Sie Schwierigkeiten haben, dies zu begreifen.
Flater

Antworten:

54

2**n -1ist auch 1 + 2 + 4 + ... + 2 n-1, was zu einer einzigen rekursiven Funktion gemacht werden kann (ohne dass die zweite 1 von der Potenz von 2 subtrahiert).

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

Lösung unten, schauen Sie nicht, ob Sie den Hinweis zuerst versuchen möchten.


Dies funktioniert, wenn ngarantiert größer als Null ist (wie in der Problemstellung tatsächlich versprochen):

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

Eine robustere Version würde auch Null- und negative Werte verarbeiten:

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)

(Das Hinzufügen eines Schecks für Nicht-Ganzzahlen bleibt als Übung übrig.)

h4z3
quelle
4
aber required_steps(0)jetzt verursacht unendliche Rekursion
Danke
7
2^0 - 1== 0. Fügen Sie iffür diesen Fall eine weitere hinzu.
h4z3
9
@ user633183 Ja, ich weiß, was eine Gesamtfunktion ist. Machst du? Weil es niemals eine Gesamtfunktion sein wird. Auch die anderen Antworten sind keine Gesamtfunktionen. Und ja, es wäre mehr Code erforderlich, um sie zu Gesamtfunktionen zu machen. - Wie gesagt, wir haben keine Domain. Was ist unsere Domain? Selbst wenn es nur so ist int, wissen wir nicht, was wir tun sollen, wenn n <0 ist. Berechnung? Einen Fehler werfen? 0 zurückgeben? In diesem Fall können wir nur eine Teilfunktion ausführen (definieren Sie sie für Dinge, von denen wir wissen, was das Ergebnis ist).
h4z3
4
Der Basisfall im OP-Code ist 0und wird n - 1für das Teilproblem verwendet. Eine Domäne natürlicher Zahlen scheint gut zu passen.
Vielen Dank, dass Sie
4
Ich danke dir sehr! Meiner bescheidenen Meinung nach ist dies die beste Lösung für mein spezifisches Problem. Ich habe keine möglichen Werte für n angegeben, wirklich leid! Ich weiß, dass das irgendwie wichtig ist ... in der Übung heißt es: "Sie können davon ausgehen, dass der Parameter n immer eine positive ganze Zahl und größer als 0 ist"
Kajice
37

Um ein Problem mit einem rekursiven Ansatz zu lösen, müssten Sie herausfinden, wie Sie die Funktion mit einer bestimmten Eingabe in Bezug auf dieselbe Funktion mit einer anderen Eingabe definieren können. In diesem Fall können Sie seitdem f(n) = 2 * f(n - 1) + 1:

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

damit:

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

Ausgänge:

0
1
3
7
15
blhsing
quelle
9

Sie können den wirklich rekursiven Teil in eine andere Funktion extrahieren

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

Oder Sie können ein Flag setzen und festlegen, wann subtrahiert werden soll

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
quelle
0

Verwenden eines zusätzlichen Parameters für das Ergebnis, 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

Sie können es auch mit bitweiser Linksverschiebung schreiben, <<-

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

Die Ausgabe ist die gleiche

Vielen Dank
quelle
2
Es ist nicht erforderlich, bitweise Operationen für eine einfache Multiplikationsübung durchzuführen. Überhaupt nicht lesbar. Auch keine Notwendigkeit für die elseKlausel in beiden Funktionen
Rafaelc
Der einzige Unterschied ist das Ändern r * 2zu r << 1und das ist "überhaupt nicht lesbar"? 😂
Vielen Dank, dass Sie
2
Inventing einen zweiten Parameter schaltet nur diese in einer Schleife , die Verschiebungen nach links nmal und dann subtrahiert 1. Scheint noch weniger elegant dann notwendig, obwohl das Ganze eine Übung in der Ineffizienz vs. ist (1<<n) - 1.
Peter Cordes
1
@PeterCordes: Das Verschieben des Status in einen Akkumulatorparameter ist die Standardmethode zum Umwandeln eines rekursiven Aufrufs in einen rekursiven Aufruf. Leider unterstützt Python nicht Proper Tail Calls, nicht einmal Proper Tail Recursion, aber das bedeutet nicht, dass dies keine nützliche Technik zum Lernen ist, damit Sie sie in anderen Sprachen anwenden können, die Proper Tail Calls implementieren oder zumindest die richtige Schwanzrekursion.
Jörg W Mittag
1
@ JörgWMittag Ja, aber es ist schwer , die Tatsache zu verschleiern , dass es wäre in diesem Fall mehr als eine Schleife natürlich. Vielleicht ist es nur so, dass ich so viel Zeit mit Assemblersprache und Performance verbringe, aber das Schreiben einer "Schleife" mithilfe der Schwanzrekursion erscheint in einer imperativen Sprache sinnlos, wenn man nur eine Schleife schreiben könnte. Oder vielleicht stört mich an dieser Antwort nur die Wahl, wie ich mich zersetzen soll: in einzelne Schichten und dann einen endgültigen Subtrahier als Basisfall. Wahrscheinlich eine Kombination aus beiden.
Peter Cordes
0

Haben Sie einen Platzhalter, um sich den ursprünglichen Wert von n zu merken n == N, und kehren Sie dann für den allerersten Schritt zurück2^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
quelle
-1

Eine Möglichkeit, den Offset von "-1" zu erhalten, besteht darin, ihn in der Rückgabe des ersten Funktionsaufrufs mit einem Argument mit einem Standardwert anzuwenden und das Offset-Argument während der rekursiven Aufrufe explizit auf Null zu setzen.

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

Zusätzlich zu den fantastischen Antworten, die zuvor gegeben wurden, wird unten die Implementierung mit inneren Funktionen gezeigt.

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))

Grundsätzlich weist es k einen globalen Wert von n zu und rekursiert ihn mit geeigneten Vergleichen durch.

Du reitest
quelle