Как написать 2 ** n - 1 как рекурсивную функцию?


49

Мне нужна функция, которая принимает n и возвращает 2 n - 1 . Это звучит достаточно просто, но функция должна быть рекурсивной. Пока у меня всего 2 н :

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

В упражнении говорится: «Можно предположить, что параметр n всегда является положительным целым числом и больше 0»


4
Просто для протокола: гораздо эффективнее делать это, как обычный человек со сдвигом и вычитанием. Целые числа Python имеют произвольную ширину, поэтому 1 << nне могут быть переполнены. Это кажется упражнением в изобретении способа разложения (1<<n) - 1на несколько шагов, возможно, устанавливая каждый бит по одному, как показывают некоторые ответы.
Питер Кордес,

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

3
@Voo: Не Карл, но, пожалуйста, перечислите мне все, что содержится вC:\MyFolder
Flater

1
@ Voo: Зависимость или нет не имеет значения для упражнения, которое сосредоточено исключительно на обучении концепции рекурсии. Я мог бы создать базовый набор классов / методов, которые могли бы использовать студенты. Вы сосредотачиваетесь на чем-то, что полностью выходит за рамки упражнения. Использование навигации по файловой системе является хорошим примером, потому что студенты обычно понимают по своей природе повторяющуюся природу папок и файлов (то есть папки могут быть вложены друг в друга почти бесконечно)
Flater

1
@ Voo Нет, я говорю, что вы можете научить рекурсии, показывая рекурсивную структуру данных. Я понятия не имею, почему вы изо всех сил пытаетесь понять это.
Флатер

Ответы:


54

2**n -1также 1 + 2 + 4 + ... + 2 n-1, которая может быть превращена в одну рекурсивную функцию (без второй, чтобы вычесть 1 из степени 2).

Подсказка : 1 + 2 * (1 + 2 * (...))

Решение ниже, не смотрите, хотите ли вы сначала попробовать подсказку.


Это работает, если nгарантированно больше нуля (как было обещано в постановке задачи):

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

Более надежная версия будет обрабатывать также нулевые и отрицательные значения:

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)

(Добавление проверки на нецелые числа оставлено в качестве упражнения.)


4
но required_steps(0)теперь вызывает бесконечную рекурсию
спасибо

7
2^0 - 1== 0. Добавьте еще один ifдля этого случая.
h4z3

9
@ user633183 Да, я знаю, что такое общая функция. Вы? Потому что это никогда не будет тотальной функцией. И другие ответы не являются полными функциями. И да, потребуется больше кода, чтобы сделать их полноценными функциями. - Как я уже сказал, у нас нет домена. Что мы должны считать нашим доменом? Даже если это просто int, мы не знаем, что делать, когда n <0. Рассчитать? Киньте ошибку? Вернуть 0? В этом случае мы можем выполнять только частичную функцию (определить ее для вещей, которые мы знаем, каков результат).
h4z3

4
Базовый случай в коде OP есть 0и используется n - 1для подзадачи. Домен Натуральных Чисел кажется подходящим.
Спасибо

4
Спасибо огромное! По моему скромному мнению, это лучшее решение для моей конкретной проблемы. Я не указал возможные значения, очень жаль! Я знаю, что это довольно важно ... упражнение гласит: «Можно предположить, что параметр n всегда является положительным целым числом и больше 0»
Kajice

37

Чтобы решить проблему с рекурсивным подходом, вы должны выяснить, как вы можете определить функцию с заданным входом в терминах той же функции с другим входом. В этом случае, так как f(n) = 2 * f(n - 1) + 1вы можете сделать:

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

так что:

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

выходы:

0
1
3
7
15

9

Вы можете извлечь действительно рекурсивную часть в другую функцию

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

Или вы можете установить флаг и определить, когда вычитать

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

Используя дополнительный параметр для результата, 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

Вы также можете написать это с помощью побитового сдвига влево, <<-

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

Выход такой же


2
Нет необходимости задействовать побитовые операции для простого упражнения умножения .. вообще не читается. Кроме того, нет необходимости в elseпредложении ни в одной функции
rafaelc

Разница только r * 2в r << 1том, что это «вообще не читается»? Thank
Спасибо

2
Придумывание второго параметра просто превращает это в цикл, который сдвигается влево nи затем вычитает 1. Кажется, даже менее изящно, чем необходимо, хотя все это упражнение по сравнению с неэффективностью (1<<n) - 1.
Питер Кордес

1
@PeterCordes: Перемещение состояния в параметр аккумулятора стандартного способ преобразования рекурсивного вызова в хвостовой рекурсии вызова. Теперь, к сожалению, Python не поддерживает Правильную хвостовую рекурсию, даже не Proper хвостовой рекурсии, но это не значит , что это не является полезным метод , чтобы узнать , так что вы можете применить его на других языках , которые делают реализации Правильной хвостовой рекурсии или хотя бы правильная рекурсия хвоста.
Йорг W Mittag

1
@ JörgWMittag Да, но в этом случае трудно скрыть тот факт, что он будет более естественным в виде цикла. Может быть, просто я трачу столько времени на язык ассемблера и производительность, но написание «цикла» с использованием хвостовой рекурсии кажется бессмысленным в императивном языке, когда вы можете просто написать цикл. Или, возможно, что беспокоит меня в этом ответе, так это просто выбор способа разложения: на смены по одному, а затем окончательное вычитание в качестве базового варианта. Вероятно, сочетание обоих.
Питер Кордес

0

Иметь заполнитель для запоминания исходного значения n, а затем для самого первого шага, т. Е. n == NВозврата2^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

Один из способов получить смещение «-1» - применить его при возврате из первого вызова функции, используя аргумент со значением по умолчанию, а затем явно установить аргумент смещения в ноль во время рекурсивных вызовов.

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

-1

Вдобавок ко всем удивительным ответам, данным ранее, ниже будет показана его реализация с внутренними функциями.

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

По сути, это присвоение глобального значения n для k и повторение через него с соответствующими сравнениями.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.