Come scrivere 2 ** n - 1 come funzione ricorsiva?


49

Ho bisogno di una funzione che accetta n e restituisce 2 n - 1 . Sembra abbastanza semplice, ma la funzione deve essere ricorsiva. Finora ho solo 2 n :

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

L'esercizio afferma: "Puoi presumere che il parametro n sia sempre un numero intero positivo e maggiore di 0"


4
Solo per la cronaca, dovrebbe essere molto più efficiente farlo come una persona normale con un turno e una sottrazione. Gli interi Python hanno una larghezza arbitraria, quindi 1 << nnon possono traboccare. Questo sembra essere un esercizio per inventare un modo per scomporre (1<<n) - 1in più passaggi, magari impostando ogni bit uno alla volta come mostrano alcune risposte.
Peter Cordes,

8
def fn(n): if n == 0: return 1; return (2 << n) - fn(0); # technically recursive
MooseBoys,

3
@Voo: Non Carl, ma per favore elencami tutto ciò che è contenuto inC:\MyFolder
Flater

1
@Voo: la dipendenza o no è irrilevante per un esercizio che si concentra esclusivamente sull'insegnamento del concetto di ricorsione. Potrei creare un insieme deriso di base di classi / metodi che gli studenti potrebbero usare. Ti stai concentrando su qualcosa che è completamente oltre il punto dell'esercizio. L'uso della navigazione nel file system è un buon esempio perché gli studenti generalmente comprendono la natura intrinsecamente ricorrente di cartelle e file (cioè le cartelle possono essere nidificate l'una nell'altra quasi indefinitamente)
Flater

1
@Voo No, sto dicendo che puoi insegnare la ricorsione mostrando una struttura di dati ricorsiva. Non ho idea del motivo per cui fai fatica a cogliere questo.
Flater,

Risposte:


54

2**n -1è anche 1 + 2 + 4 + ... + 2 n-1 che può trasformarsi in una singola funzione ricorsiva (senza che la seconda sottragga 1 dalla potenza di 2).

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

Soluzione di seguito, non guardare se vuoi provare prima il suggerimento.


Funziona se nè garantito che è maggiore di zero (come effettivamente promesso nella dichiarazione del problema):

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

Una versione più robusta gestirà anche i valori zero e negativo:

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'aggiunta di un controllo per i numeri non interi viene lasciata come un esercizio.)


4
ma required_steps(0)ora provoca una ricorsione infinita
Grazie

7
2^0 - 1== 0. Aggiungine un altro ifper quel caso.
h4z3,

9
@ user633183 Sì, so cos'è una funzione totale. Fai? Perché non sarà mai una funzione totale. Né le altre risposte sono funzioni totali. E sì, sarebbe necessario più codice per renderli funzioni totali. - Come ho detto, non abbiamo alcun dominio. Cosa dovremmo supporre sia il nostro dominio? Anche se è giusto int, non sappiamo cosa fare quando n <0. Calcolare? Hai lanciato un errore? Ritorna 0? In questo caso, possiamo solo svolgere una funzione parziale (definirla per cose che sappiamo quale sia il risultato).
h4z3,

4
Il caso base nel codice dell'OP è 0e utilizza n - 1per il sottoproblema. Un dominio di numeri naturali sembra una buona scelta.
Grazie

4
Grazie mille! A mio modesto parere, questa è la soluzione migliore per il mio problema specifico. Non ho indicato i valori possibili per n, mi dispiace davvero! So che è un po 'importante ... l'esercizio afferma: "Si può presumere che il parametro n sia sempre un numero intero positivo e maggiore di 0"
Kajice,

37

Per risolvere un problema con un approccio ricorsivo, dovresti scoprire come definire la funzione con un determinato input in termini della stessa funzione con un input diverso. In questo caso, poiché f(n) = 2 * f(n - 1) + 1puoi fare:

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

così che:

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

uscite:

0
1
3
7
15

9

È possibile estrarre la parte realmente ricorsiva in un'altra funzione

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

Oppure puoi impostare un flag e definire solo quando sottrarre

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

>>> print(required_steps(10))
1023

0

Utilizzando un parametro aggiuntivo per il risultato, 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

Puoi anche scriverlo usando lo spostamento a sinistra bit a bit, <<-

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

L'output è lo stesso


2
Non è necessario coinvolgere operazioni bit a bit per un semplice esercizio di moltiplicazione .. non è affatto leggibile. Inoltre, non è necessaria la elseclausola in nessuna delle due funzioni
rafaelc,

L'unica differenza sta cambiando r * 2in r << 1e che "non è affatto leggibile"? 😂
Grazie

2
Inventare un secondo parametro appena trasforma questo in un ciclo che si sposta a sinistra nvolte e poi sottrae 1. sembra ancor meno elegante allora necessaria, anche se il tutto è un esercizio di inefficienza rispetto (1<<n) - 1.
Peter Cordes,

1
@PeterCordes: spostare lo stato in un parametro accumulatore è il modo standard di trasformare una chiamata ricorsiva in una chiamata ricorsiva di coda. Ora, purtroppo, Python non supporta le chiamate di coda corretta, nemmeno corretto ricorsione in coda, ma questo non vuol dire che non si tratta di una tecnica utile per imparare in modo da poter applicare in altre lingue che fanno implementare le chiamate di coda Proper o almeno una corretta ricorsione della coda.
Jörg W Mittag,

1
@ JörgWMittag Sì, ma in questo caso è difficile nascondere il fatto che sarebbe più naturale come un ciclo. Forse è solo che dedico così tanto tempo al linguaggio assembly e alle prestazioni, ma scrivere un "loop" usando la ricorsione della coda sembra inutile in un linguaggio imperativo quando potresti semplicemente scrivere un loop. O forse ciò che mi preoccupa di questa risposta è solo la scelta di come scomporre: in turni uno alla volta e poi una sottrazione finale come base. Probabilmente una combinazione di entrambi.
Peter Cordes,

0

Chiedi a un segnaposto di ricordare il valore originale di n e quindi per il primo passo n == N, ovvero restituire2^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)

-1

Un modo per ottenere l'offset di "-1" è applicarlo nel ritorno dalla prima chiamata di funzione utilizzando un argomento con un valore predefinito, quindi impostare esplicitamente l'argomento offset su zero durante le chiamate ricorsive.

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

-1

Oltre a tutte le fantastiche risposte fornite in precedenza, di seguito verrà mostrata la sua implementazione con funzioni interne.

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

Fondamentalmente, assegna un valore globale da n a k e lo ricorre attraverso di esso con confronti appropriati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.