Python: la secuencia Collatz
On diciembre 1, 2020 by admin¿Puedo obtener una revisión de mi código para la secuencia Collatz del capítulo tres de Automatizar las cosas aburridas con Python ?
La secuencia Collatz
Escriba una función llamada collatz () que tenga un parámetro llamado número. Si el número es par, collatz () debería imprimir el número // 2 y devolver este valor. Si el número es impar, entonces collatz () debería imprimir y devolver 3 * número + 1.
Luego, escriba un programa que le permita al usuario escribir un entero y que siga llamando a collatz () en ese número hasta que La función devuelve el valor 1. (Sorprendentemente, esta secuencia realmente funciona para cualquier número entero; tarde o temprano, con esta secuencia, llegará a 1. Incluso los matemáticos no están seguros de por qué. Su programa está explorando lo que se llama la secuencia de Collatz , a veces llamado «el problema matemático imposible más simple».)
Recuerde convertir el valor de retorno de input () a un número entero con la función int (); de lo contrario, será un valor de cadena.
Sugerencia: un número entero es par si el número% 2 == 0, y es impar si el número% 2 == 1.
La salida de este programa podría verse así:
Enter number: 3 10 5 16 8 4 2 1
Validación de entrada
Agregue declaraciones try y except al proyecto anterior para detectar si el usuario escribe una cadena que no sea entera. Normalmente, la función int () generará un error ValueError si se le pasa una cadena no entera, como en int («puppy»). En la cláusula except, imprima un mensaje para el usuario diciéndole que debe ingresar un número entero.
Me pregunto principalmente si hay una manera más limpia de escribir mi solución.
def collatz(num): while num > 1: if num % 2 == 0: print(num//2) num = num //2 elif num % 2 ==1: print(3*num+1) num = 3*num+1 else: print(num) def getNum(): global num num = (input("> ")) try: num = int(num) except ValueError: print("plese enter a number") getNum() getNum() collatz(num)
Comentarios
- Por favor, agregue una descripción que indique lo que recomienda este capítulo 3 con ejemplos de entrada y salida, nadie ‘ va a adivinar qué es eso
- @Edmad Broctor y @ Carcigenicate Gracias por sus comentarios. Seguí adelante y revisé mi publicación.
- » Sorprendentemente, esta secuencia en realidad funciona para cualquier número entero; tarde o temprano, con esta secuencia, llegarás en 1! » En realidad, ‘ no sabemos que funciona para cualquier número entero, la idea de que siempre llegará a 1 es una conjetura .
- @PierreCath é Tengo una demostración verdaderamente maravillosa de esta proposición que este cuadro de comentarios es demasiado pequeño para contener.
- @Acccumulation Guau ! Eso ‘ es impresionante, en ese caso debe actualizar el artículo de Wikipedia y comunicarse con la Unión Matemática Internacional para recoger su Medalla Fields.
Respuesta
Primero, observe cómo está duplicando cálculos:
print(num//2) num = num //2
Esto puede no causar problemas con este código específico, pero no es una buena práctica. Estás haciendo el doble de trabajo del necesario, lo que puede causar problemas de rendimiento una vez que comienzas a escribir un código más complicado. Haz el cálculo una vez y guarda el resultado. En este caso, sin embargo, todo lo que necesitas hacer es invertir esas líneas y use num
:
num = num // 2 print(num)
Además, asegúrese de tener el espacio adecuado con los operadores y sea coherente .
Su if
y elif
los casos son exclusivos entre sí, y su else
nunca debería suceder. Si la primera condición es verdadera, la otra debe ser falsa y viceversa. No es necesario que segundo cheque. Una vez reescrito, verá que la impresión en todos los casos no es necesaria. Puede imprimir después de:
while num > 1: if num % 2 == 0: num = num // 2 else: num = 3 * num + 1 print(num)
Ya que «simplemente está reafirmando num
una de las dos opciones basadas en un condición, una expresión condicional se puede utilizar aquí limpiamente también:
while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) print(num)
Las llaves no son necesarias, pero creo que son útiles aquí debido a la número de operadores involucrados.
Imprimir los números no es ideal aquí. En la mayoría de los códigos, debe poder usar los datos que produce. Si quisiera analizar la secuencia producida, tendría que hacer algo para interceptar la salida estándar, que es costosa y demasiado complicada. Conviértalo en una función que acumule y devuelva una lista.En los siguientes ejemplos, también agregué algunas sugerencias de tipo para aclarar cuál es el tipo de datos:
from typing import List def collatz(starting_num: int) -> List[int]: nums = [starting_num] num = starting_num while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) nums.append(num) return nums
O, un enfoque mucho más limpio es convertirlo en un generador que produzca los números :
# Calling this function returns a generator that produces ints # Ignore the two Nones, as they aren"t needed for this kind of generator def collatz_gen(starting_num: int) -> Generator[int, None, None]: yield starting_num num = starting_num while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) yield num >>> list(collatz_gen(5)) [5, 16, 8, 4, 2, 1]
Hay algunas cosas notables sobre getNum
:
Python usa «snake_case» , no «camelCase».
Su uso de global num
aquí es innecesario y confuso . Al igual que antes, explícitamente return
cualquier dato que produzca la función:
def get_num() -> int: raw_num = input("> ") try: return int(raw_num) except ValueError: print("Please enter a number") return get_num()
Observe cómo en lugar de reasignar un global num
, solo estamos devolviendo el número. También espacié un poco las cosas y usé algunos nombres más apropiados. Conceptualmente, diría que num = input("> ")
es incorrecto. En el momento en que se ejecuta, num
no contener un número (contiene una cadena).
Este no es un buen uso de la recursividad. Es probable que no le cause ningún problema, pero si su usuario es realmente tonto e ingresa datos incorrectos ~ 1000 veces, su programa fallará. Solo use un bucle:
def get_num() -> int: while True: raw_num = input("> ") try: return int(raw_num) except ValueError: print("Please enter a number")
En lenguajes como Python, tenga cuidado al usar la recursividad en los casos en los que no tenga garantías de cuántas veces se repetirá la función.
I «d también probablemente nombre esto algo más cercano a ask_for_num
. «get» no deja muy claro de dónde provienen los datos.
En conjunto, «terminará con:
from typing import Generator def collatz_gen(starting_num: int) -> Generator[int, None, None]: yield starting_num num = starting_num while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) yield num def ask_for_num() -> int: while True: raw_num = input("> ") try: return int(raw_num) except ValueError: print("Please enter a number")
Que se puede utilizar como:
num = ask_for_num() for n in collatz_gen(num): print(n)
Respuesta
Prompt
La mala práctica más obvia aquí es el uso de una variable global. En lugar de establecer num
como efecto secundario, su función debería return
el resultado.
getNum()
no es un buen nombre para la función. PEP 8 , la guía de estilo oficial de Python, dice que los nombres de las funciones deben ser lower_case_with_underscores
. Además, «obtener» implica que la función está recuperando un dato que ya está almacenado en algún lugar, lo que no es el caso aquí. Finalmente, «Num» debería ser más específico.
El uso de recursividad no es apropiado. Si quieres un bucle, escribe un bucle.
def ask_integer(): """ Return an integer entered by the user (repeatedly prompting if the input is not a valid integer). """ while True: try: return int(input("> ")) except ValueError: print("Please enter an integer") num = ask_integer()
collatz
función
Estrictamente hablando, no siguió las instrucciones. Su solución no es incorrecta o mala, simplemente no implementó la función collatz
de acuerdo con la especificación que se proporcionó, que dice que debe imprimir y devolver un solo número.
def collatz(num): """ Given a number, print and return its successor in the Collatz sequence. """ next = num // 2 if num % 2 == 0 else 3 * num + 1 print(next) return next num = ask_integer() while num > 1: num = collatz(num)
Comentarios
- Quizás revertir la prueba y eliminar la comparación con 0:
next = 3 * num + 1 if num % 2 else num // 2
- @RootTwo Honestamente, creo que eso enturbia la intención. Nosotros ‘ re sin comprobar si
num % 2
es falso; ‘ no es un predicado. Nosotros ‘ re comprobando si ‘ es igual a 0. Da la casualidad de que ‘ son equivalentes en Python. -
next = ...
eclipsa elnext
. En este contexto, ‘ probablemente no sea peligroso, pero es bueno tenerlo en cuenta - Aunque técnicamente podrías usar
if num % 2 else
, estoy de acuerdo en que mostrar la intención aquí es más importante. yif num % 2 == 0 else
comunica mejor esa intención. Sin embargo, estoy de acuerdo en que debería cambiar el nombre de su variablenext
. Es ‘ un mal hábito. - @ 200_success Gracias por señalar que no devolví el número, no simplemente lo imprimí. Eso fue muy útil.
Respuesta
Para completar, una implementación recursiva para collatz
(ya tiene suficientes buenas sugerencias para ingresar num
):
def collatz(num): print(num) if num == 1: return num if num % 2 == 0: return collatz(num // 2) return collatz(3 * num + 1) collatz(3)
Salidas
3 10 5 16 8 4 2 1
Comentarios
- No ‘ t creo que esta respuesta ayuda a OP. Aunque una definición recursiva es ciertamente posible, no ‘ se ajusta a la especificación que están tratando de cumplir. Además, este código tiene cierta redundancia: la función siempre devuelve 1 (cuando devuelve algo), entonces, ¿por qué tener un valor de retorno?
- @MeesdeVries
the function always returns 1 (when it returns at all), so why even have a return value
‘ no estoy seguro de seguirlo. La recursividad debe tener un caso base. Da la casualidad de que el caso base en este ejemplo es ifnum == 1
. El resultado del ejemplo muestra que la función no siempre devuelve 1. - La función imprime otros valores, pero devuelve solo el valor 1.
- No estoy seguro de cuál es su punto . Mi comentario original señaló que no es necesario que la función que escribió devuelva nada, porque siempre será el número 1, independientemente de la entrada. Por lo tanto, una mejor implementación evitaría devolver cualquier valor, para evitar la sugerencia de que es significativo. Por ejemplo, diría que una mejor implementación (de la parte de recursión) es
if num % 2 == 0: collatz(num//2); elif num > 1: collatz(3 * num + 1)
. - Te invito a que pruebes el código que escribí por ti mismo para ver que no lo hace (al menos, para la entrada de números enteros positivos). Si desea seguir hablando de esto, le sugiero que lo llevemos al chat.
Deja una respuesta