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"


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


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

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

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


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)

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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.