Jak napisać 2 ** n - 1 jako funkcję rekurencyjną?

49

Potrzebuję funkcji, która pobiera n i zwraca 2 n - 1 . Brzmi to dość prosto, ale funkcja musi być rekurencyjna. Do tej pory mam tylko 2 n :

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

Ćwiczenie stwierdza: „Możesz założyć, że parametr n jest zawsze dodatnią liczbą całkowitą i jest większy od 0”

Kajice
źródło
4
Dla przypomnienia, powinno być znacznie wydajniej robić to jak normalna osoba z przesunięciem i odejmowaniem. Liczby całkowite w języku Python mają dowolną szerokość, więc 1 << nnie mogą się przepełnić. Wydaje się, że jest to ćwiczenie polegające na wynalezieniu sposobu rozpadu (1<<n) - 1na wiele etapów, być może ustawienie każdego z bitów pojedynczo, jak pokazują niektóre odpowiedzi.
Peter Cordes
8
def fn(n): if n == 0: return 1; return (2 << n) - fn(0); # technically recursive
MooseBoys
3
@Voo: Nie Carl, ale proszę wymienić wszystko, co zawieraC:\MyFolder
Flater
1
@Voo: Zależność, czy nie, nie ma znaczenia dla ćwiczenia, które koncentruje się wyłącznie na nauczaniu pojęcia rekurencji. Mógłbym stworzyć podstawowy szyderczy zestaw klas / metod, z których uczniowie mogliby korzystać. Koncentrujesz się na czymś, co jest zupełnie poza punktem ćwiczenia. Dobrym przykładem jest nawigacja w systemie plików, ponieważ studenci zazwyczaj rozumieją z natury powtarzalną naturę folderów i plików (tzn. Foldery mogą być zagnieżdżone w sobie prawie w nieskończoność)
Flater,
1
@Voo Nie Mówię, że możesz nauczyć rekurencji, pokazując rekurencyjną strukturę danych. Nie mam pojęcia, dlaczego tak się starasz.
Flater

Odpowiedzi:

54

2**n -1to także 1 + 2 + 4 + ... + 2 n-1, które można przekształcić w jedną funkcję rekurencyjną (bez drugiej, aby odjąć 1 od potęgi 2).

Wskazówka : 1 + 2 * (1 + 2 * (...))

Rozwiązanie poniżej, nie patrz, czy chcesz najpierw wypróbować podpowiedź.


Działa nto, jeśli jest gwarantowane, że jest większe od zera (tak jak faktycznie obiecano w opisie problemu):

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

Bardziej niezawodna wersja obsługiwałaby również wartości zerowe i ujemne:

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)

(Dodanie czeku dla liczb całkowitych jest pozostawione jako ćwiczenie).

h4z3
źródło
4
ale required_steps(0)teraz powoduje nieskończoną rekurencję
Dziękuję
7
2^0 - 1== 0. Dodaj kolejną ifdla tego przypadku.
h4z3
9
@ user633183 Tak, wiem, co to jest całkowita funkcja. Czy ty? Ponieważ nigdy nie będzie to całkowita funkcja. Inne odpowiedzi nie są też funkcjami całkowitymi. I tak, potrzeba więcej kodu, aby były one pełnymi funkcjami. - Jak powiedziałem, nie mamy domeny. Co powinniśmy założyć, że nasza domena jest? Nawet jeśli jest to po prostu int, nie wiemy, co robić, gdy n <0. Oblicz? Zgłaszać błąd? Zwrócić 0? W takim przypadku możemy wykonać tylko funkcję częściową (zdefiniować ją dla rzeczy, które wiemy, jaki jest wynik).
h4z3
4
Podstawowym przypadkiem w kodzie OP jest 0i wykorzystuje n - 1podproblem. Domena liczb naturalnych wydaje się być dobrze dopasowana.
Dziękuję
4
Dziękuję bardzo! Moim skromnym zdaniem jest to najlepsze rozwiązanie mojego konkretnego problemu. Nie podałem możliwych wartości dla n, naprawdę przepraszam! Wiem, że jest to trochę ważne ... ćwiczenie mówi: „Możesz założyć, że parametr n jest zawsze dodatnią liczbą całkowitą i jest większy od 0”
Kajice
37

Aby rozwiązać problem z podejściem rekurencyjnym, musisz dowiedzieć się, w jaki sposób możesz zdefiniować funkcję z danym wejściem w kategoriach tej samej funkcji z innym wejściem. W takim przypadku f(n) = 2 * f(n - 1) + 1możesz wykonać:

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

po to aby:

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

wyjścia:

0
1
3
7
15
blhsing
źródło
9

Możesz wyodrębnić naprawdę rekurencyjną część do innej funkcji

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

Lub możesz ustawić flagę i określić, kiedy odjąć

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
źródło
0

Używając dodatkowego parametru dla wyniku, 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

Możesz także napisać, używając bitowego przesunięcia w lewo, <<-

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

Dane wyjściowe są takie same

Dziękuję Ci
źródło
2
Nie jest konieczne angażowanie operacji bitowych dla prostego ćwiczenia mnożenia. W ogóle nieczytelne. Również nie ma potrzeby elseklauzuli w żadnej z funkcji
rafaelc
Jedyną różnicą jest zmiana r * 2na r << 1„w ogóle nieczytelna”? 😂
Dziękuję
2
Wymyślanie 2nd parametr to po prostu zamienia się w pętli, która przesuwa się w lewo nrazy, a następnie odejmuje 1. wydaje się jeszcze mniej elegancki wtedy konieczne, chociaż cała sprawa jest ćwiczeniem w nieskuteczności vs. (1<<n) - 1.
Peter Cordes
1
@PeterCordes: Przesuwanie w stan parametru akumulatora jest standardowy sposób przekształcania rekurencyjnego połączenia do połączenia ogona rekurencyjnej. Teraz, niestety, Python nie obsługuje Prawidłowych Wywołań Ogona, nawet Prawidłowej Rekonstrukcji Ogona, ale to nie znaczy, że nie jest to przydatna technika do nauki, dzięki czemu można ją zastosować w innych językach, które implementują Prawidłowe Wywołania Ogona lub przynajmniej Właściwa Rekurencja Ogona.
Jörg W Mittag
1
@ JörgWMittag Tak, ale w tym przypadku trudno ukryć fakt, że byłby bardziej naturalny jako pętla. Może po prostu spędzam tyle czasu na asemblerze i wydajności, ale pisanie „pętli” za pomocą rekurencji ogona wydaje się bezcelowe w języku imperatywnym, kiedy można po prostu napisać pętlę. A może w tym pytaniu niepokoi mnie tylko wybór sposobu rozłożenia: na zmiany jednorazowe, a następnie odejmowanie końcowe jako podstawa. Prawdopodobnie połączenie obu.
Peter Cordes
0

Miej symbol zastępczy, aby zapamiętać oryginalną wartość n, a następnie dla pierwszego kroku, tj. n == NPowrotu2^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
źródło
-1

Jednym ze sposobów uzyskania przesunięcia „-1” jest zastosowanie go w zwrocie z pierwszego wywołania funkcji za pomocą argumentu o wartości domyślnej, a następnie jawne ustawienie argumentu przesunięcia na zero podczas wywołań rekurencyjnych.

def required_steps(n, offset = -1):
    if n == 0:
        return 1
    return offset + 2 * required_steps(n-1,0)
Eric Towers
źródło
-1

Oprócz wszystkich wspaniałych odpowiedzi podanych wcześniej, poniżej pokażę jego implementację z funkcjami wewnętrznymi.

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

Zasadniczo przypisuje globalną wartość od n do k i rekursuje ją za pomocą odpowiednich porównań.

Jedziesz
źródło