Factorial en Python

Más ejercicios resueltos

Si deseas revisar más ejercicios resueltos, haz click en el siguiente botón. 

Calcular el factorial de un número es uno de los problemas clásicos que se suelen analizar cuando se inicia en el mundo del paradigma de programación modular. Es típico que se desarrollen ejemplos de la función factorial en esta fase. En este artículo, analizamos al detalle y paso a paso, funciones para el cálculo del factorial en Python presentando 4 alternativas de solución a través de diversas versiones usando estructuras algorítmicas iterativas, la recursión así como la programación dinámica. Si deseas conocer con más detalle la definición del factorial usada en este problema, haz click en el siguiente enlace.

Parte de esta análisis se encuentra sintetizado en el vídeo «Factorial Iterativo en Python» en nuestro canal de YouTube. Te invitamos a que lo visites.

A continuación presentamos las 4 alternativas de solución que nos permitirán entender cómo se calculan los factoriales en Python. Se analizará cada alternativa con sumo detalle. Las versiones que se analizarán son las siguientes:

  • Programa principal.
  • Versión usando la instrucción \texttt{while}.
  • Versión usando la instrucción \texttt{for}.
  • Versión recursiva.
  • Versión usando Programación Dinámica.

Programa principal

Realizaremos la implementación del programa principal. Este será el mismo para las primeras 3 versiones. Tiene básicamente 3 objetivos:

  •  Leer el dato de entrada.
  • Validar el dato leído.
  • Invocar a la función que va a realizar el cálculo del factorial.
 

Para la lectura de datos se utiliza la función \texttt{input}. El dato leído será almacenado en la variable \texttt{n}. Usamos la función \texttt{int} para transformar la cadena de caracteres leída en un entero. 

La validación la realizaremos con una selectiva doble que en Python se implementa con la estructura de control \texttt{if..else}. Se emitirá un mensaje informativo si es que el número es menor que cero pues el factorial no está definido para números negativos.

Una vez que la variable se ha validado, se procede a invocar a la función factorial usando como argumento el número \texttt{n} leído. La invocación se realiza al momento de realizar la impresión. En la impresión, además, estamos incluyendo al lado del número, el carácter de admiración (!) para que se imprima el número con la notación más conocida para el factorial. 

n=int(input("Ingrese número n (n>=0): "))
if n < 0 :
    print("Debe ingresar un número mayor o igual a cero")
else:
    print("{:d}!={:d}".format(n, factorial(n))) 

Función Factorial: versión con instrucción while

Pasaremos ahora a diseñar la primera versión. Esta versión permitirá calcular el factorial de un número en Python usando un ciclo while. Antes de describir la implementación, analizaremos los datos de entrada y de salida de esta función. La función \texttt{factorial} recibe como parámetro el número \texttt{n}. Se asume que este número \texttt{n} es mayor o igual que cero. La función retorna la productoria que contiene el factorial del parámetro \texttt{n}. Recuerde que la sentencia \texttt{def}, permite crear funciones definidas por el usuario en Python.

El siguiente paso a realizar es el control de flujo. La implementación de esta versión se basa en una estructura iterativa con entrada controlada. Diseñaremos el control de flujo usando un contador, al que se le suele denominar variable de control. Hemos nombrado esta variable de control con el identificador \texttt{i}. Como deseamos generar todos los factores de la productoria, inicializamos la variable de control con el valor de 2. La iniciamos en 2  dado que 1 es el elemento neutro para la nultiplicación, por lo tanto multiplicar por 1 no afecta en nada a la productoria. De esta manera nos ahorramos una iteración.

En Python, la entrada controlada se implementa mediante la instrucción \texttt{while}. Colocamos como condición de la iteración \texttt{i<=n}. Esto significa que la iteración se realizará mientras esta condición se cumpla. Como tenemos que garantizar que la iteración finalice en algún instante del tiempo, actualizamos la variable de control. Al finalizar de la iteración, incrementamos su valor en 1. De esta manera en la próxima iteración \texttt{i} contendrá el valor del siguiente factor a utilizar para el cálculo de la productoria. Para el incremento hemos usado el operador de asignación \texttt{+=}. Recuerde que \texttt{i+=1} equivale a \texttt{i=i+1}

La productoria se almacena convenientemente en la variable llamada \texttt{productoria}. Inicializamos a la productoria con el valor de 1 por ser el elemento neutro para la multiplicación. Y actualizamos su valor dentro de la iteración. En la actualización simplemente multiplicamos el valor de la \texttt{productoria} por \texttt{i}, nos aseguramos que esta actualización se realice antes de actualizar la variable \texttt{i}. Para la actualización de la productoria hemos usado el operador de asignación \texttt{*=}. Recuerde que \texttt{productoria*=i} equivale a \texttt{productoria=productoria*i}.

El valor que retorna la función se indica a través de la sentencia \texttt{return}.

def factorial(n):
    productoria = 1
    i = 2
    while i <= n:
        productoria *= i
        i+=1
    return productoria 

Función Factorial: versión con instrucción for

A continuación, procederemos a analizar la implementación del cálculo del factorial usando la instrucción for en Python. La instrucción \texttt{for} en Python es ligeramente diferente a la que podemos encontramos en lenguajes como ANSI C, C++, Pascal o Java. En ANSI C, C++ y Java el usuario debe definir la condición de la iteración, así como el paso que tendrá la variable de control en cada iteración. En Pascal y PSeInt por ejemplo, se debe definir la secuencia de números sobre la cuál se va a iterar indicando el número inicial y el número final de dicha secuencia. 

En Python, la instrucción \texttt{for} itera sobre secuencias pero de cualquier tipo, pudiendo ser éstas números, listas, cadenas de caracteres entre otros.

Para el caso del factorial, necesitamos iterar sobre los números que se encuentra en el rango del [2..n]. Para generar una secuencia de números, Python ofrece una función de soporte llamada \texttt{range}. En nuestro caso hemos usado \texttt{range(2, n+1)}. El primer parámetro, en este caso \texttt{2}, corresponde con el primer número de la secuencia. El segundo parámetro, en este caso \texttt{n+1}, corresponde con el último número del rango, pero hay que tener en consideración que dentro de la secuencia de números, no se retorna el \texttt{n+1}. En realidad \texttt{range} genera un intervalo semiabierto por la derecha, matemáticamente se suele representar \texttt{[a,b[} o \texttt{[a,b)}. Entonces, la función \texttt{range(2, n+1)} retorna una secuencia de números en el rango [2,n+1[, o lo que es lo mismo de [2,n].

Range generará diferentes valores de \texttt{i} en cada ciclo, usamos este hecho para generar la productoria deseada. Para esto se inicializa la \texttt{productoria} en 2  y en cada ciclo, se multiplica por el valor de \texttt{i}.

def factorial(n):
    productoria = 1
    for i in range(2, n+1):
        productoria *= i
    return productoria 

Función Factorial: versión recursiva

La cuarta versión a implementar es la versión recursiva. Quizás sea la más simple de entender pues prácticamente es la representación en Python de la definición recursiva. Los algoritmos recursivos suelen ser simples de implementar pero en muchas ocasiones no son tan eficientes como las implementaciones iterativas.

def factorial(n):
    if n<=1:
        return 1
    return n*factorial(n-1) 

Función Factorial: versión usando Programación Dinámica

Algoritmos como el cálculo de factorial poseen una característica, para hallar el valor de n!, debe calcularse (n-1)!. Esto se realiza independientemente de la versión y no importa si la versión es iterativa o recursiva.

Esto no ofrecería ningún problema si es que la función se ejecuta una sola vez, pero ¿qué pasa cuando en un programa necesitamos invocar varias veces a la función factorial? Pues, en ese caso las soluciones implementadas no serian eficientes. Por ejemplo, imagínese que hacemos las invocaciones a las funciones \texttt{factorial(4)}\texttt{factorial(6)}. En este caso hipotético se calcularían dos veces el \texttt{factorial(4)} lo que es un uso innecesario de tiempo.

¿Cómo evitamos recalcular tantas veces los factoriales? Pues existe una técnicas de programación llamada programación dinámica que justamente busca reducir el tiempo de ejecución de los algoritmos. Esto lo logra reusando resultados obtenidos previamente. Estos resultados suelen ser almacenados en arreglos o matrices. 

Función principal

Vamos a diseñar la función principal para el cálculo del factorial. Hay ligeros cambios en relación a las versiones anteriores. Los dos primeros objetivos son los mismo pero el tercer y cuarto son diferentes.

  •  Leer el dato de entrada.
  • Validar el dato leído.
  • Invocar al módulo que va a realizar la inicialización del arreglo.
  • Retornar el valor del factorial usando el arreglo inicializado.
 
 
El primer gran cambio que vemos es que no existe una función \texttt{factorial}. Entonces ¿cómo se hace para hallar el valor del factorial? Pues en lugar de ello se usa un arreglo, al que convenientemente hemos denominado \texttt{factorial}. Dicho arreglo va a contener el valor de los factoriales de forma que \texttt{factorial[0]} contendrá el valor del factorial de \texttt{0}, \texttt{factorial[1]} contendrá el valor del factorial de \texttt{1} y en general \texttt{factorial[n]} contendrá el valor del factorial de \texttt{n}.

Resulta muy sencillo el retornar los factoriales pero ¿cómo se inicializa dicho arreglo? Pues este es en realidad el problema de la programación dinámica. Debemos llenar el arreglo con el valor de los factoriales antes de usar el arreglo. Esta inicialización la debemos hacer una única vez durante todo el algoritmo.

El arreglo ha sido declarado con la instrucción \texttt{numpy.arrage[MAX\_FACTORIALES+1]}. Se inicializa con un espacio para 21 elementos. Los índices válido para el arreglo serán [0..20]

import numpy

MAX_FACTORIALES=20        
n=int(input("Ingrese número n (0<=n<=20): "))
if n < 0 or n >MAX_FACTORIALES:
    print("Debe ingresar un número en el rango [0..20]")
else:    
    factorial=numpy.arange(MAX_FACTORIALES+1)
    inicializar_arreglo(factorial, MAX_FACTORIALES)
    print("{:d}!={:d}".format(n, factorial[n]))
 

Inicialización del arreglo

A continuación presentamos el algoritmo para inicializar el arreglo. Básicamente lo que hacemos es inicializar el primer elemento con el valor de \texttt{0}. Luego en un ciclo iterativo, que en realidad puede ser usando cualquiera de las versiones previamente analizadas, se actualizan los demás elementos del arreglo. En dicha actualización siempre se usa el valor anterior.

def inicializar_arreglo(factorial, n):
    factorial[0] = 1
    for i in range(2, n+1):
        factorial[i] = i * factorial[i - 1] 

Conclusión

Hemos presentado en este artículo, 4 alternativas de solución al cálculo del factorial usando Python. Se ha utilizado estructuras iterativas con entrada controladasalida controladarecursión y programación dinámica.  Podrá descargar la solución propuesta en el repositorio GitHub de iterando++ a través del siguiente enlace

Hemos preparado otros artículos adicionales en donde describimos al detalle la implementación de este problema PSeInt y en otros lenguajes de programación. Te invitamos a leer los siguientes artículos de iterando++

Si te interesa profundizar más en el desarrollo en Python, los dos mejores libros que se han escrito son Learning Python de Mark Lutz y Python Crash Course de Eric Matthes.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *