Como escrever 2 ** n - 1 como uma função recursiva?

49

Eu preciso de uma função que leva n e retorna 2 n - 1 . Parece bastante simples, mas a função precisa ser recursiva. Até agora eu tenho apenas 2 n :

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

O exercício declara: "Você pode assumir que o parâmetro n é sempre um número inteiro positivo e maior que 0"

Kajice
fonte
4
Apenas para constar, deve ser muito mais eficiente fazê-lo como uma pessoa normal, com uma troca e subtração. Os inteiros do Python têm largura arbitrária, portanto 1 << nnão podem exceder. Este parece ser um exercício de inventar uma maneira de decompor-se (1<<n) - 1em várias etapas, talvez definindo cada bit um de cada vez, como mostram algumas respostas.
Peter Cordes
8
def fn(n): if n == 0: return 1; return (2 << n) - fn(0); # technically recursive
MooseBoys 15/10/19
3
@Voo: Não Carl, mas lista, por favor me tudo o que está contidoC:\MyFolder
Flater
11
@Voo: Dependência ou não é irrelevante para um exercício que se concentra puramente no ensino do conceito de recursão. Eu poderia criar um conjunto de classes / métodos zombados básicos que os alunos pudessem usar. Você está se concentrando em algo que está completamente além do objetivo do exercício. Usando a navegação do sistema de arquivos é um bom exemplo porque os estudantes geralmente compreender a natureza inerentemente recorrente de pastas e arquivos (ou seja, as pastas podem ser aninhados um no outro quase indefinidamente)
Flater
11
@Voo Não, estou dizendo que você pode ensinar recursão mostrando uma estrutura de dados recursiva. Eu não tenho idéia do por que você luta para entender isso.
Flater

Respostas:

54

2**n -1é também 1 + 2 + 4 + ... + 2 n-1 que pode ser transformado em uma única função recursiva (sem a segunda para subtrair 1 da potência de 2).

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

Solução abaixo, não procure se você quiser tentar a dica primeiro.


Isso funciona se né garantido que seja maior que zero (como foi realmente prometido na declaração do 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)

Uma versão mais robusta também lidaria com valores zero e negativos:

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)

(Adicionar uma verificação para números não inteiros é deixado como um exercício.)

h4z3
fonte
4
mas required_steps(0)agora causa recursão infinita
Obrigado
7
2^0 - 1== 0. Adicione outro ifpara esse caso.
H4z3 14/10/19
9
@ user633183 Sim, eu sei o que é uma função total. Você? Porque nunca será uma função total. As outras respostas também não são funções totais. E sim, seriam necessários mais códigos para torná-los funções totais. - Como eu disse, não temos domínio. O que devemos assumir que é o nosso domínio? Mesmo se for apenas int, não sabemos o que fazer quando n <0. Calcular? Lançar um erro? Retornar 0? Nesse caso, só podemos fazer uma função parcial (defina-a para coisas que sabemos qual é o resultado).
H4z3 14/10/19
4
O caso base no código do OP é 0e é usado n - 1para o subproblema. Um domínio de números naturais parece ser um bom ajuste.
Obrigado
4
Muito obrigado! Na minha humilde opinião, esta é a melhor solução para o meu problema específico. Não indiquei valores possíveis para n, desculpe! Eu sei que isso é meio importante ... o exercício declara: "Você pode assumir que o parâmetro n é sempre um número inteiro positivo e maior que 0"
Kajice
37

Para resolver um problema com uma abordagem recursiva, você teria que descobrir como definir a função com uma entrada especificada em termos da mesma função com uma entrada diferente. Nesse caso, desde f(n) = 2 * f(n - 1) + 1, você pode fazer:

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

de modo a:

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

saídas:

0
1
3
7
15
blhsing
fonte
9

Você pode extrair a parte realmente recursiva para outra função

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

Ou você pode definir um sinalizador e definir exatamente quando subtrair

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

Usando um parâmetro adicional para o resultado, 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

Você também pode escrevê-lo usando o deslocamento à esquerda bit a bit, <<-

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

A saída é a mesma

Obrigado
fonte
2
Não é necessário envolver operações bit a bit para um simples exercício de multiplicação. Não é legível. Além disso, não há necessidade da elsecláusula em qualquer função
rafaelc
A única diferença está mudando r * 2para r << 1e "isso não é legível"? 😂
Obrigado
2
Inventando um segundo parâmetro apenas transforma isso em um loop que mudanças deixaram nvezes e, em seguida, subtrai 1. Parece ainda menos elegante, então, necessário, embora a coisa toda é um exercício de ineficiência vs. (1<<n) - 1.
Peter Cordes
11
@PeterCordes: Mover o estado para um parâmetro acumulador é a maneira padrão de transformar uma chamada recursiva em uma chamada recursiva de cauda. Agora, infelizmente, Python não suporta chamadas de cauda adequada, nem mesmo Proper cauda recursão, mas isso não significa que isso não é uma técnica útil para aprender para que você possa aplicá-lo em outras línguas que fazer implementar chamadas de cauda Proper ou pelo menos recursão adequada da cauda.
Jörg W Mittag
11
@ JörgWMittag Sim, mas neste caso é difícil disfarçar o fato de que seria mais natural como um loop. Talvez seja apenas o fato de eu dedicar tanto tempo à linguagem assembly e ao desempenho, mas escrever um "loop" usando recursão de cauda parece inútil em uma linguagem imperativa quando você pode simplesmente escrever um loop. Ou talvez o que mais me incomode com essa resposta seja apenas a escolha de como decompor: em turnos de uma vez e depois em uma subtração final como base. Provavelmente uma combinação de ambos.
Peter Cordes
0

Tenha um espaço reservado para lembrar o valor original de n e, em seguida, para o primeiro passo n == N, ou seja , retornar2^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
fonte
-1

Uma maneira de obter o deslocamento de "-1" é aplicá-lo no retorno da primeira chamada de função usando um argumento com um valor padrão e defina explicitamente o argumento de deslocamento como zero durante as chamadas recursivas.

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

Além de todas as respostas impressionantes fornecidas anteriormente, a seguir mostramos sua implementação com funções internas.

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

Basicamente, está atribuindo um valor global de n a ke recorrendo a ele com comparações apropriadas.

Seu IDE
fonte