¿Cómo escribir 2 ** n - 1 como una función recursiva?

49

Necesito una función que tome n y devuelva 2 n - 1 . Suena bastante simple, pero la función tiene que ser recursiva. Hasta ahora solo tengo 2 n :

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

El ejercicio dice: "Se puede suponer que el parámetro n es siempre un número entero positivo y mayor que 0"

Kajice
fuente
44
Solo para el registro, debería ser mucho más eficiente hacerlo como una persona normal con un turno y una resta. Los enteros de Python tienen un ancho arbitrario, por 1 << nlo que no pueden desbordarse. Esto parece ser un ejercicio para inventar una forma de descomponerse (1<<n) - 1en múltiples pasos, quizás estableciendo cada bit uno a la vez, como muestran algunas respuestas.
Peter Cordes
8
def fn(n): if n == 0: return 1; return (2 << n) - fn(0); # technically recursive
MooseBoys
3
@Voo: No Carl, pero por favor enumérame todo lo que contieneC:\MyFolder
Flater
1
@Voo: La dependencia o no es irrelevante para un ejercicio que se centra exclusivamente en enseñar el concepto de recursión. Podría hacer un conjunto básico burlado de clases / métodos que los estudiantes podrían usar. Te estás enfocando en algo que está completamente fuera del objetivo del ejercicio. El uso de la navegación del sistema de archivos es un buen ejemplo porque los estudiantes generalmente entienden la naturaleza inherentemente recurrente de las carpetas y archivos (es decir, las carpetas se pueden anidar entre sí de forma indefinida)
Flater
1
@Voo No, digo que puedes enseñar la recursividad mostrando una estructura de datos recursiva. No tengo idea de por qué te cuesta entender esto.
Flater

Respuestas:

54

2**n -1también es 1 + 2 + 4 + ... + 2 n-1 que puede convertirse en una única función recursiva (sin la segunda para restar 1 de la potencia de 2).

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

Solución a continuación, no busque si desea probar la pista primero.


Esto funciona si nse garantiza que es mayor que cero (como se prometió en la declaración 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 versión más robusta también manejaría valores cero y 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)

(Agregar un cheque para los no enteros se deja como ejercicio).

h4z3
fuente
44
pero required_steps(0)ahora causa una recursión infinita
Gracias
77
2^0 - 1== 0. Agregue otro ifpara ese caso.
h4z3
99
@ user633183 Sí, sé lo que es una función total. ¿Vos si? Porque nunca será una función total. Las otras respuestas tampoco son funciones totales. Y sí, se necesitaría más código para que sean funciones totales. - Como dije, no tenemos dominio. ¿Cuál deberíamos asumir que es nuestro dominio? Incluso si es solo int, no sabemos qué hacer cuando n <0. ¿Calcular? ¿Lanzar un error? ¿Devolver 0? En este caso, solo podemos hacer una función parcial (definirla para cosas que sabemos cuál es el resultado).
h4z3
44
El caso base en el código del OP es 0y se utiliza n - 1para el subproblema. Un dominio de números naturales parece un buen ajuste.
Gracias
44
Muchas gracias! En mi humilde opinión, esta es la mejor solución para mi problema específico. No dije valores posibles para n, ¡lo siento mucho! Sé que eso es algo importante ... el ejercicio dice: "Se puede suponer que el parámetro n es siempre un número entero positivo y mayor que 0"
Kajice
37

Para resolver un problema con un enfoque recursivo, tendría que averiguar cómo puede definir la función con una entrada dada en términos de la misma función con una entrada diferente. En este caso, desde entonces f(n) = 2 * f(n - 1) + 1, puedes hacer:

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

así que eso:

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

salidas:

0
1
3
7
15
blhsing
fuente
9

Puedes extraer la parte realmente recursiva a otra función

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

O puede establecer una bandera y definir cuándo sustraer

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

Usando un parámetro adicional para el 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

También puede escribirlo usando el desplazamiento a la izquierda a nivel de bit, <<-

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

La salida es la misma

Gracias
fuente
2
No es necesario involucrar operaciones bit a bit para un simple ejercicio de multiplicación ... no legible en absoluto. Además, no es necesaria la elsecláusula en ninguna de las funciones
rafaelc
La única diferencia está cambiando r * 2a r << 1y que es "no se puede leer en absoluto"? 😂
Gracias
2
La invención de un segundo parámetro solo se convierte esto en un bucle que se desplazamientos a la izquierda nveces y luego le resta 1. parece aún menos elegante a continuación, es necesario, a pesar de todo el asunto es un ejercicio de comparación ineficiencia (1<<n) - 1.
Peter Cordes
1
@PeterCordes: Mover el estado a un parámetro acumulador es la forma estándar de transformar una llamada recursiva en una llamada recursiva de cola. Ahora, por desgracia, Python no admite llamadas de cola adecuados, ni siquiera adecuada recursión de cola, pero eso no quiere decir que esto no es una técnica útil para aprender de manera que se puede aplicar en otros idiomas que hacen poner en práctica las llamadas apropiadas cola o al menos Recursión de cola adecuada.
Jörg W Mittag
1
@ JörgWMittag Sí, pero en este caso es difícil ocultar el hecho de que sería más natural como un bucle. Tal vez es solo que paso tanto tiempo en lenguaje ensamblador y rendimiento, pero escribir un "bucle" usando la recursión de cola parece inútil en un lenguaje imperativo cuando puedes escribir un bucle. O quizás lo que me molesta acerca de esta respuesta es solo la elección de cómo descomponerse: en turnos uno a la vez y luego una resta final como el caso base. Probablemente una combinación de ambos.
Peter Cordes
0

Tenga un marcador de posición para recordar el valor original de n y luego para el primer paso n == N, es decir , regresar2^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
fuente
-1

Una forma de obtener el desplazamiento de "-1" es aplicarlo en el retorno de la primera llamada de función utilizando un argumento con un valor predeterminado, luego establecer explícitamente el argumento de desplazamiento en cero durante las llamadas recursivas.

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

Además de todas las increíbles respuestas dadas anteriormente, a continuación se mostrará su implementación con funciones 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))

Básicamente, está asignando un valor global de n a k y recurriendo a través de él con las comparaciones apropiadas.

Montas
fuente