1 - Variables y Tipos de Datos

Entender cómo funcionan las variables y los tipos de datos es fundamental para dominar cualquier lenguaje de programación. En este artículo repasaremos los conceptos básicos sobre variables, operadores, tipos de datos y conversiones de tipo utilizando el lenguaje Python. Cubriremos tanto la teoría como ejemplos prácticos para que puedas aplicar estos conceptos en tus propios programas.

Variables

Una variable es un contenedor para almacenar datos en la memoria de la computadora. Podemos pensar en ella como una caja con una etiqueta. La etiqueta es el nombre de la variable y dentro de la caja se almacena su valor.

Para declarar una variable en Python solo escribimos el nombre y le asignamos un valor:

edad = 28
precio = 19.95
soltero = True

Los nombres de variables deben comenzar con letras o guión bajo, y sólo pueden contener letras, números y guiones bajos. Se recomienda usar nombres significativos que representen el propósito de la variable.

En Python las variables no necesitan ser declaradas con un tipo particular. El tipo se infiere automáticamente al asignar el valor:

edad = 28 # edad es de tipo entero (int)
precio = 19.95 # precio es de tipo float
estudiante = True # soltero es de tipo booleano

Una vez asignada, una variable puede cambiar su valor en cualquier momento:

edad = 30 # Cambiamos edad a 30

Alcance y tiempo de vida

El alcance de una variable se refiere a las partes del código donde está disponible. Las variables declaradas fuera de funciones son globales y están disponibles en todo el archivo. Las variables dentro de una función son locales y solo visibles dentro de ella.

El tiempo de vida es el período durante el cual existe la variable en memoria. Las variables locales existen mientras se ejecuta la función, luego son destruidas. Las globales existen mientras el programa está en ejecución.

Asignación

La asignación con el operador = permite cambiar o inicializar el valor de una variable:

numero = 10
numero = 20 # Ahora numero vale 20

También existen los operadores de asignación compuesta como += y -= que combinan una operación y asignación:

numero += 5 # Suma 5 a numero (numero = numero + 5)
numero -= 2 # Resta 2 a numero

Tipos de datos

Los tipos de datos definen el tipo de valor que puede almacenar una variable. Python tiene varios tipos incorporados, incluyendo:

Numéricos: Para almacenar valores numéricos como enteros, flotantes, complejos:

entero = 10
flotante = 10.5
complejo = 3 + 4j

Cadenas: Para almacenar texto:

texto = "Hola Mundo"

Booleano: Para valores lógicos Verdadero o Falso:

variable_verdadera = True
variable_falsa = False

Colecciones: Para almacenar múltiples valores como listas, tuplas y diccionarios:

  • Listas: Secuencias mutables de valores:

    lista = [1, 2, 3]
    
  • Tuplas: Secuencias inmutables de valores:

    tupla = (1, 2, 3)
    
  • Diccionarios: Estructuras de pares llave-valor:

    diccionario = {"nombre":"Juan", "edad": 20}
    

Es importante elegir el tipo de dato que mejor represente la información que queremos almacenar.


Operadores

Los operadores nos permiten realizar operaciones con valores y variables en Python. Algunos operadores comunes son:

  • Aritméticos: +, -, *, /, %, //, **

  • Comparación: ==, !=, >, <, >=, <=

  • Lógicos: and, or, not

  • Asignación: =, +=, -=, *=, /=

Veamos ejemplos concretos de expresiones usando estos operadores en Python:

# Aritméticos
5 + 4 # Suma, resultado 9
10 - 3 # Resta, resultado 7
4 * 5 # Multiplicación, resultado 20

# Comparación
5 > 4 # Mayor que, resultado Verdadero
7 < 10 # Menor que, resultado Verdadero

# Lógicos
True and False # Resultado False
True or False # Resultado True
not True # Resultado False

# Asignación
numero = 10
numero += 5 # Suma 5 a numero, equivalente a numero = numero + 5

Cada tipo de operador trabaja con tipos de datos específicos. Debemos usarlos de forma consistente según el tipo de datos de nuestras variables.


Conversiones de tipo

A veces necesitamos convertir un tipo de dato a otro para realizar ciertas operaciones. En Python podemos convertir de forma explícita o implícita:

Explícita: Usando funciones como int(), float(), str():

flotante = 13.5
entero = int(flotante) # convierte 13.5 a 13

texto = "100"
numero = int(texto) # convierte "100" a 100

Implícita: Python convierte automáticamente en algunos casos:

entero = 100
flotante = 3.5
resultado = entero + flotante # resultado es 103.5, entero se convirtió a float

Algunas conversiones pueden generar pérdida de datos o errores:

flotante = 13.5
entero = int(flotante)

print(entero) # 13, se pierden los decimales

Para prevenir esto debemos elegir explícitamente conversiones que tengan sentido para nuestros datos.


Conclusión

En este artículo revisamos conceptos clave como variables, operadores, tipos de datos y conversiones en Python. Aplicar bien estos conceptos te permitirá manipular datos de forma eficiente en tus programas. Recomiendo practicar con ejemplos propios para ganar experiencia en usar estas características. ¡Éxitos en tu aprendizaje de Python!


2 - Operaciones de Entrada y Salida

Las operaciones de entrada y salida (input/output o I/O) permiten que un programa se comunique e intercambie datos con el mundo exterior. En este artículo veremos en detalle operaciones de entrada desde el teclado o un archivo, y salida hacia la pantalla o un archivo.

Salida a pantalla

Python también provee funciones para enviar la salida de un programa a la “salida estándar”, generalmente la pantalla o terminal1.

La función print() muestra el valor pasado como parámetro:

nombre = "Eric"
print(nombre) # muestra "Eric"

Podemos imprimir múltiples valores separados por comas2:

print("Hola", nombre, "!") # muestra "Hola Eric!"

También podemos usar valores literales sin asignar a variables3:

print("2 + 3 =", 2 + 3) # muestra "2 + 3 = 5"

Formateo de salida

Python provee varias formas de dar formato a la salida4:

f-Strings: Permiten insertar variables dentro de una cadena:

nombre = "Eric"
print(f"Hola {nombre}") # muestra "Hola Eric"

%s: Inserta cadenas de texto en una cadena de formato:

nombre = "Eric"
print("Hola %s" % nombre) # muestra "Hola Eric"

%d: Inserta números enteros:

valor = 15
print("El valor es %d" % valor) # muestra "El valor es 15"

.format(): Inserta valores en una cadena de formato:

nombre = "Eric"
print("Hola {}. Bienvenido".format(nombre))
# muestra "Hola Eric. Bienvenido"

Estas opciones de formateo nos permiten interpolar variables y valores en cadenas de texto para generar outputs personalizados. Podemos combinar múltiples valores y formateos en una sola cadena de salida2.


Entrada desde el teclado

Python provee funciones incorporadas para leer datos ingresados por el usuario en tiempo de ejecución. Esto se conoce como “entrada estándar”4.

La función input() permite leer un valor ingresado por el usuario y asignarlo a una variable. Por ejemplo:

nombre = input("Ingresa tu nombre: ")

Esto muestra el mensaje “Ingresa tu nombre: " y espera a que el usuario escriba un texto y presione Enter. Ese valor se asigna a la variable nombre2.

La función input() siempre regresa una cadena de texto. Si queremos pedir un número u otro tipo de dato, debemos convertirlo usando int(), float(), etc1:

edad = int(input("Ingresa tu edad: "))
pi = float(input("Ingresa el valor de pi: "))

Leyendo múltiples valores

Podemos pedir y leer varios valores en una misma línea separándolos con comas3:

nombre, edad = input("Ingresa nombre y edad: ").split()

El método split() divide la entrada en partes y retorna una lista de cadenas. Luego asignamos los elementos de la lista a variables separadas.

También podemos leer varias líneas de entrada con un ciclo4:

nombres = [] # lista vacía

for x in range(3):
   nombre = input("Ingresa un nombre: ")
   nombres.append(nombre)

Este código lee 3 nombres ingresados por el usuario y los agrega a una lista.


Salida a un archivo

Además de imprimir a pantalla, podemos escribir la salida a un archivo usando la función open()1:

archivo = open("datos.txt", "w")

Esto abre datos.txt para escritura (“w”) y retorna un objeto archivo.

Luego usamos archivo.write() para escribir a ese archivo3:

archivo.write("Hola mundo!")
archivo.write("Este texto va al archivo")

Debemos cerrar el archivo con archivo.close() cuando terminamos4:

archivo.close()

También podemos usar with para abrir y cerrar automáticamente2:

with open("datos.txt", "w") as archivo:
   archivo.write("Hola mundo!")
   # no hace falta cerrar, es automático

Lectura de archivos

Para leer un archivo usamos open() con modo “r” y iteramos sobre el objeto archivo1:

with open("datos.txt", "r") as archivo:
   for linea in archivo:
      print(linea) # muestra cada línea del archivo

Esto imprime cada línea, incluyendo los saltos de línea.

Podemos leer todas las líneas a una lista con readlines()3:

linenas = archivo.readlines()
print(linenas)

Para leer el contenido completo a una cadena usamos read()4:

texto = archivo.read()
print(texto)

También podemos leer un número determinado de bytes o caracteres con read(n)2.


Operaciones para el manejo de archivos

Existen varias funciones incorporadas para manipular archivos en Python1:

  • open() - Abre un archivo y retorna un objeto archivo
  • close() - Cierra el archivo
  • write() - Escribe datos al archivo
  • read() - Lee datos del archivo
  • readline() - Lee una línea del archivo
  • truncate() - Vacía el archivo
  • seek() - Mueve la posición de lectura/escritura
  • rename() - Renombra el archivo
  • remove() - Elimina el archivo

Estas funciones nos permiten efectuar operaciones avanzadas para leer, escribir y mantener archivos.


Conclusión

En este artículo explicamos en detalle operaciones de entrada y salida en Python, incluyendo leer de entrada estándar y escribir a salida estándar o archivos. Manejar correctamente la entrada y salida es esencial para muchas aplicaciones de Python. Recomiendo practicar con ejemplos propios para dominar estas funciones3.



Referencias


  1. McKinney, W. (2018). Python for data analysis: Data wrangling with Pandas, NumPy, and IPython. O’Reilly Media. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Lutz, M. (2013). Learning Python: Powerful Object-Oriented Programming. O’Reilly Media, Incorporated. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. Matthes, E. (2015). Python crash course: A hands-on, project-based introduction to programming. No Starch Press. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. Downey, A. B. (2015). Think Python: How to think like a computer scientist. Needham, Massachusetts: Green Tea Press. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

3 - Control de Flujo

Cuando nos embarcamos en la emocionante travesía de aprender a programar, no tardamos en descubrir que la programación no es solo acerca de escribir código, sino también sobre cómo controlar el flujo de ese código. Podemos compararlo con el flujo de decisiones que tomamos en nuestra vida diaria. Por ejemplo, si hace frío afuera, nos ponemos un abrigo antes de salir. Si no tenemos tareas pendientes, vamos al cine. Nuestras acciones dependen de estas evaluaciones y decisiones. El control de flujo es, en esencia, la manera en que decidimos qué parte del código se ejecuta, cuándo se ejecuta y cuántas veces lo hace. Para ello, contamos con una variedad de estructuras que nos permiten tomar decisiones, repetir acciones y dividir nuestro código en bloques lógicos.

Condiciones: tomando decisiones en el código

La vida está llena de decisiones: “Si llueve, llevaré un paraguas. De lo contrario, usaré anteojos de sol”. Estas decisiones también están presentes en el mundo de la programación. Las condiciones son como preguntas que la computadora se hace. Nos permiten tomar decisiones y ejecutar código específico dependiendo de una condición1. Pueden ser simples como “¿Está lloviendo?” o complejas como “¿Es fin de semana y tengo menos de $100 en mi cuenta bancaria?”.

if

La estructura if nos permite evaluar condiciones y tomar decisiones basadas en el resultado de esa evaluación.

edad = 15

if edad >= 18:
    print("Eres mayor de edad")

El código anterior permite ejecutar una porción de código si la edad de una persona es mayo o igual a 18 años.

if-else

Cuando se desea ejecutar un código alternativo si la condición es falsa, utilizamos la estructura if-else

edad = 21
if edad >= 18:
    print("Eres mayor de edad")
else:
    print("Eres menor de edad")

En este caso, se determina si la persona es mayor de edad, o menor de edad, el mensaje mostrado es diferente

if-elif-else

Cuando las condiciones son múltiples y no es suficientes con dos caminos, se utiliza la estructura if-elif-else para evaluarlas forma encadenada.

edad = 5
if edad <= 13:
    print("Eres un niño")
elif edad > 13 and edad < 18:
    print("Eres un adolescente")
else:
    print("Eres un adulto")

En el código anterior se observan tres caminos claros, uno para cuando la edad es menor o igual a 13 años, otro para cuando la edad esta entre 13 y 18 y otro para cuando es mayor o igual a 18.

Otra manera de resolver este problema es mediante la estructura switch-case, que, aunque Python no incorpora de manera nativa, como si lo hacen otros lenguajes como Java o C++, es una herramienta importante para conocer. Esta estructura permite a los programadores manejar múltiples condiciones de manera más organizada que una serie de if-elif-else.

En Java, por ejemplo:

int dia = 3;
switch(dia) {
    case 1:
        System.out.println("Lunes");
        break;
    case 2:
        System.out.println("Martes");
        break;
    case 3:
        System.out.println("Miércoles");
        break;
    // ... y así sucesivamente
    default:
        System.out.println("Día no válido");
}

En el ejemplo anterior, dependiendo del valor de dia, se imprimirá el día correspondiente2.


Bucles: repitiendo acciones

A veces, en programación, necesitamos repetir una acción varias veces. En lugar de escribir el mismo código varias veces, podemos usar bucles. Estos, permiten repetir la ejecución de un bloque de código mientras se cumpla una condición3.

while

El bucle while es útil cuando queremos repetir una acción basada en una condición.

# Imprime del 1 al 5
i = 1
while i <= 5:
    print(i)
    i = i + 1

do-while

Similar a while pero garantiza al menos una ejecución dado que primero se ejecuta el bloque de código y luego se evalúa la condición. Python no implementa esta estructura, pero otros lenguajes como Java y C++ sí lo hacen.

int i = 1;

do {
    System.out.println(i);
    i++;
} while(i <= 5);
int numero = 0;
do {
    std::cout << "Hola, mundo!" << std::endl;
    numero++;
} while (numero < 5);

for

El bucle for es útil cuando sabemos cuántas veces queremos repetir una acción.

for i in range(5):
    print("Hola, mundo!")

El código anterior imprimirá “Hola, mundo!” cinco veces.

También podemos iterar sobre los elementos de una lista u objeto iterable:

nombres = ["María", "Florencia", "Julián"]
for nombre in nombres:
    print(f"Hola {nombre}")

# Imprime
# Hola María
# Hola Florencia
# Hola Julián

Las sentencias break y continue

Podemos usar break para terminar el bucle y continue para saltar a la siguiente iteración.

El break se usa para terminar completamente el bucle cuando se cumple una condición, en el ejemplo siguiente, cuando i llega a 5.

# Ejemplo de break
i = 0
while i < 10:
    print(i)
    if i == 5:
        break
    i += 1

# Imprime:
# 0
# 1
# 2
# 3
# 4
# 5

El continue se usa para saltarse una iteración del bucle y continuar con la siguiente cuando se cumple una condición. Aquí lo usamos para saltarnos los números pares.

# Ejemplo de continue
i = 0
while i < 10:
    i += 1
    if i % 2 == 0:
        continue
    print(i)

# Imprime:
# 1
# 3
# 5
# 7
# 9

Anidamiento: combinando estructuras

Las estructuras de control de flujo pueden anidarse dentro de otras. Por ejemplo, podemos tener bucles dentro de bucles o condiciones dentro de bucles.

for i in range(5):
    for j in range(10):
        if (i % 2 == 0 and j % 3 == 0):
            print(f"i = {i}, j = {j}")

Este código imprimirá combinaciones de i y j sólo cuando i sea divisible por 2 y j sea divisible por 3, demostrando cómo los bucles se anidan y se ejecutan3.


Patrones de uso comunes

Existen patrones específicos para resolver necesidades habituales con control de flujo.

Búsqueda

Buscar un valor en una colección:

frutas = ["manzana", "naranja"]

buscando = "naranja"
encontrado = False

for fruta in frutas:
    if fruta == buscando:
        encontrado = True
        break

if encontrado:
    print("Fruta encontrada!")

Acumulación

Acumular valores incrementales en un bucle.

total = 0

for i in range(10):
    total += i

print(total) # Suma de 0..9 = 45

Diagramas de flujo: la ruta visual hacia el entendimiento del código

Los programadores, sin importar si son principiantes o expertos, a menudo se encuentran enfrentando desafíos que requieren una planificación detallada antes de sumergirse en el código. Aquí es donde los diagramas de flujo entran en juego como una herramienta esencial. Estos diagramas son representaciones gráficas de los procesos y la lógica detrás de un programa o sistema. En este artículo, desentrañaremos el mundo de los diagramas de flujo, desde sus conceptos básicos hasta las técnicas avanzadas, y cómo pueden beneficiar a programadores de todos los niveles.

Un diagrama de flujo es una representación gráfica de un proceso. Utiliza símbolos específicos para representar diferentes tipos de instrucciones o acciones. Su objetivo principal es simplificar la comprensión de un proceso, mostrando paso a paso cómo fluye la información o las decisiones. Estos diagramas:

  • Facilitan la comprensión de procesos complejos.
  • Ayudan en la fase de diseño y planificación de un programa.
  • Sirven como documentación y referencia para futuros desarrollos.

Los diagramas de flujo son una herramienta poderosa que no solo beneficia a los principiantes, sino también a los programadores experimentados. Ofrecen una visión clara y estructurada de un proceso o programa, facilitando la planificación, el diseño y la comunicación entre los miembros del equipo.

Elementos básicos

Los diagramas de flujo constan de varios símbolos, cada uno con un significado específico:

  • Ovalo: Representa el inicio o el fin de un proceso.
  • Rectángulo: Denota una operación o instrucción.
  • Diamante: Indica una decisión basada en una condición.
  • Flechas: Muestran la dirección del flujo.
graph TD;
    start((Inicio))
    process[Proceso]
    decision{¿Repetir?}
    final((Final))

    start --> process;
    process --> decision;
    decision --> |Si| process
    decision --> |No| final

Ejemplos

Vamos a diseñar un diagrama de flujo para un programa que pida un número y nos diga si es par o impar.

graph TB
    inicio((Inicio))
    entrada[Ingresar número]
    decision{¿Es par?}
    esPar[Es par]
    esImpar[Es impar]
    final((Final))

    inicio --> entrada
    entrada --> decision
    decision --> |Si| esPar
    decision --> |No| esImpar
    esPar --> final
    esImpar --> final

Conforme los programas se vuelven más complejos, es posible que necesites incorporar bucles, múltiples condiciones y otros elementos avanzados en tu diagrama de flujo. Por ejemplo, aquí diagramamos un programa que sume los números desde el 1 al número ingresado por el usuario.

graph TD
    inicio((Inicio))
    entrada[Ingresar número]
    setVariables[Establecer suma=0 y contador=1]
    bucle_condicion{¿contador <= N?}
    bucle_codigo[Sumar valor e incrementar el contador]
    resultado[Mostrar suma]
    final((Final))

    inicio --> entrada
    entrada --> setVariables
    setVariables --> bucle_condicion
    bucle_condicion --> |Si| bucle_codigo
    bucle_codigo --> bucle_condicion
    bucle_condicion --> |No| resultado
    resultado --> final

Conclusión

El control de flujo es el corazón de la programación. Sin él, los programas serían secuencias lineales de acciones sin la capacidad de tomar decisiones o repetir tareas. Al dominar estas estructuras, no solo mejoras tu capacidad para escribir código, sino también tu capacidad para pensar lógicamente y resolver problemas complejos.



Referencias


  1. Lutz, M. (2013). Learning Python: Powerful Object-Oriented Programming. O’Reilly Media, Incorporated. ↩︎

  2. Deitel, P., & Deitel, H. (2012). Java: How to program. Upper Saddle River, NJ: Prentice Hall. ↩︎

  3. Matthes, E. (2015). Python crash course: A hands-on, project-based introduction to programming. San Francisco, CA: No Starch Press. ↩︎ ↩︎

4 - Funciones

En el vasto y emocionante mundo de la programación, existen conceptos que son pilares fundamentales para cualquier desarrollador, sin importar su nivel de experiencia. Uno de estos conceptos es el de las funciones. ¿Qué son? ¿Por qué son tan cruciales? ¡Vamos a descubrirlo!

¿Qué son las funciones?

Una función, en términos simples, es un bloque de código que se ejecuta sólo cuando es llamado. Puedes pensar en ella como un pequeño programa dentro de tu programa principal, diseñado para realizar una tarea específica1. Una función también puede verse como una caja negra: le pasamos una entrada (parámetros), ocurre algún procesamiento interno, y produce una salida (retorno).

Las funciones nos permiten segmentar nuestro código en partes lógicas, donde cada parte realiza una única acción. Esto brinda varios beneficios2:

  • Reutilización: Una vez definida la función, podemos ejecutar (llamar) ese código desde cualquier lugar de nuestro programa cuantas veces sea necesario.
  • Organización: Permite dividir un programa grande en partes más pequeñas y manejables.
  • Encapsulamiento: Las funciones reducen la complejidad escondiendo los detalles de implementación internos.
  • Mantenimiento: Si necesitamos realizar cambios, solo debemos modificar el código en un lugar (la función) en lugar de rastrear todas las instancias de ese código.

Procedimientos vs. Funciones

Es vital distinguir entre estos dos conceptos. Mientras que una función siempre devuelve un valor, un procedimiento realiza una tarea pero no devuelve nada. En algunos lenguajes, esta diferencia es más clara que en otros. Python, por ejemplo, tiene funciones que pueden o no devolver valores.


Anatomía de una función

En Python, una función se declara usando la palabra clave def, seguida del nombre de la función y paréntesis. El código dentro de la función se denomina el cuerpo de la función3 y contiene el conjunto de instrucciones a ejecutar para cumplir con su tarea..

def mi_funcion():
    print("¡Hola desde mi función!")

Para llamar o invocar una función, simplemente usamos su nombre seguido de paréntesis:

mi_funcion()  # Salida: ¡Hola desde mi función!

Parámetros y argumentos

Las funciones se vuelven aún más poderosas cuando les pasamos información, conocida como parámetros. Estos actúan como “variables” dentro de la función y permiten que la función trabaje con diferentes datos cada vez que se llama.

Mientras que los parámetros son variables definidas en la definición de la función. Los argumentos son los valores reales pasados al llamar a la función.

def saludo(nombre):
    print(f"¡Hola {nombre}!")


saludo("María")
# Salida:
#   ¡Hola María!

Podemos definir valores por defecto para los parámetros Python permite parámetros por defecto, que tienen un valor predeterminado, lo cual hace opcional pasar esos argumentos al llamar la función. También permite parámetros nombrados que permiten pasar los argumentos en cualquier orden, especificando su nombre.

def saludo(nombre="María", repeticiones=3):
    repeticion = 1
    while repeticion <= repeticiones:
        print(f"¡Hola {nombre}!")
        repeticion += 1


saludo()
# Salida:
#   ¡Hola María!
#   ¡Hola María!
#   ¡Hola María!


saludo("Florencia", 4)
# Salida:
#   ¡Hola Florencia!
#   ¡Hola Florencia!
#   ¡Hola Florencia!
#   ¡Hola Florencia!


saludo(repeticiones=2, nombre="Julián")
# Salida
#   ¡Hola Julián!
#   ¡Hola Julián!

Retorno de valores

Las funciones pueden devolver un resultado o valor de retorno usando la palabra reservada return.

def area_circulo(radio):
    return 3.14 * (radio ** 2)


resultado = area_circulo(10)
print(resultado) # Salida: 314

El valor de retorno se pasa de vuelta a donde se llamó la función y se puede asignar a una variable para usarlo.

Las funciones también pueden ejecutar alguna tarea sin devolver nada explícitamente. En Python esto se conoce como retornar None.


Variables locales y globales

Las variables locales se definen dentro de una función y solo existen en ese ámbito, mientras que las variables globales están definidas fuera y pueden ser accedidas desde cualquier parte del código. Es crucial entender su alcance (dónde puede ser accesible una variable) y duración (cuánto tiempo vive una variable).

x = 10 # x es global

def suma():
    y = 5 # y es local
    return x + y

suma()    # Salida: 15
print(y)  # Error, y no existe fuera de la función

Podemos leer variables globales desde una función, pero si necesitamos modificarla debemos declararla global.

x = 10

def suma():
    global x
    x = x + 5

suma()
print(x) # 15

Buenas prácticas

Al crear funciones debemos seguir ciertos principios y patrones4:

  • El nombre de una función debe indicar claramente su propósito.
  • Hacer las funciones pequeñas, simples y enfocadas en una tarea. Una función debe hacer una cosa y hacerla bien.
  • Utilizar nombres descriptivos para las funciones y sus parámetros.
  • Evitar efectos secundarios y modificación de variables globales.
  • Documentar adecuadamente el propósito y uso de cada función.
  • Limitar el número de parámetros, idealmente de 0 a 3 parámetros.

Seguir estas buenas prácticas nos ayudará a crear funciones reutilizables, encapsuladas y fáciles de mantener.


Conclusión

Las funciones son componentes fundamentales en la programación, permitiéndonos organizar, reutilizar y encapsular código. Definiendo funciones que realicen una sola tarea mantenemos nuestros programas simplificados, fáciles de entender y modificar. Al comprender y dominar este concepto, no solo mejoras la calidad de tu código sino también tu eficiencia como desarrollador.



Referencias


  1. McConnell, S. (2004). Code Complete. Microsoft Press. ↩︎

  2. Joyanes Aguilar, L. (2008). Fundamentos de programación: algoritmos, estructura de datos y objetos. McGraw-Hill. ↩︎

  3. Python Software Foundation. (2022). Documentación oficial de Python↩︎

  4. Kindler, E., & Krivy, I. (2011). Object-Oriented Simulation of systems with Java: A working introduction. BoD–Books on Demand. ↩︎

5 - Funciones Recursivas

La recursión es un concepto fundamental en programación que permite que una función se llame a sí misma. A primera vista puede parecer contraintuitivo, pero dominar este enfoque abre la puerta a soluciones elegantes para ciertos problemas.

Recursión: el arte de llamarse a sí mismo

Imagina una caja de espejos donde cada espejo refleja lo que ve en el siguiente, creando una serie infinita de reflejos. La recursión en programación es algo similar. Es una técnica donde una función se llama a sí misma directa o indirectamente[^1^]. Esto crea un ciclo en el cual la función resuelve un problema dividiéndolo en instancias más pequeñas del mismo problema, hasta llegar a un caso base sencillo de resolver.

Por ejemplo, imaginemos una función que imprime un contador regresivo:

def cuenta_regresiva(numero):

    if numero > 0:
        print(numero)
        cuenta_regresiva(numero - 1)
    else:
        print("¡Despegue!")

cuenta_regresiva(5)

Esta función se llama recursivamente reduciendo el número cada vez hasta llegar a 0, y luego imprime el mensaje de despegue.

La recursión es un enfoque declarativo donde se enfoca en dividir un problema en casos recursivos sin necesidad de controlar explícitamente el bucle usando iteradores o contadores como en la programación imperativa.


La estructura de una función recursiva

El poder de la recursión radica en su simplicidad. Sin embargo, es esencial entender su estructura para evitar caer en trampas comunes. Una función recursiva típica tiene dos partes principales1:

  1. Caso base: El caso más simple con una solución conocida que no requiere recursión. Es la condición de parada, que detiene la recursión. Sin el caso base, caeríamos en una recursión infinita que eventualmente desborda la pila de llamadas.
  2. Caso recursivo: Es donde ocurre la mágica llamada recursiva. En este punto, la función se llama a sí misma con un argumento modificado, generalmente una versión reducida del problema original.

Ejemplos clásicos de recursión

Factorial

El factorial de un entero positivo \(n\) es el producto de todos los enteros positivos menores o iguales a \(n\). Por ejemplo:

  • \(5! = 5 * 4 * 3 * 2 * 1 = 120\)
  • \(4! = 4 * 3 * 2 * 1 = 24\)
  • \(3! = 3 * 2 * 1 = 6\)

Aquí está el código en Python para calcular el factorial usando recursión:

def factorial(n):
    if n == 1:
        return 1              # Caso base
    return n * factorial(n-1) # Caso recursivo
  • Caso base: El caso base es la instancia más simple y pequeña del problema que puede responderse directamente. Para el factorial, cuando \(n = 1\), el resultado es \(1\).
  • Caso recursivo: Si \(n\) es mayor que \(1\), la función se llama a sí misma con \(n-1\), y multiplica el resultado por \(n\).

Digamos que quieres calcular el factorial de \(5\), así que llamas a factorial(5).

Esto es lo que sucede:

  1. Paso 1: Como \(n = 5\) no es \(1\), la función llama a factorial(4), luego multiplica el resultado por \(5\).
  2. Paso 2: Ahora, dentro de factorial(4), \(n = 4\), entonces la función llama a factorial(3), luego multiplica el resultado por \(4\).
  3. Paso 3: Dentro de factorial(3), \(n = 3\), así que llama a factorial(2), luego multiplica el resultado por \(3\).
  4. Paso 4: Dentro de factorial(2), \(n = 2\), así que llama a factorial(1), luego multiplica el resultado por \(2\).
  5. Paso 5: Finalmente, factorial(1) alcanza el caso base, donde \(n = 1\), así que retorna \(1\).

Ahora los resultados se desenrollan:

  • factorial(2) retorna \(2 * 1 = 2\)
  • factorial(3) retorna \(3 * 2 = 6\)
  • factorial(4) retorna \(4 * 6 = 24\)
  • factorial(5) retorna \(5 * 24 = 120\)

El resultado final es \(120\), que es el valor de \(5!\).

Aquí hay una representación visual de la pila de llamadas:

factorial(5)
  -> factorial(4)
    -> factorial(3)
      -> factorial(2)
        -> factorial(1)
          return 1
        return 2
      return 6
    return 24
  return 120

Serie de Fibonacci

La serie de Fibonacci es una secuencia de números donde cada número es la suma de los dos anteriores. Comienza con \(0\) y \(1\), y cada número posterior es la suma de los dos números anteriores. Los primeros números de la secuencia son: \(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …\)

Aquí está el código en Python para calcular el \(n^th\) número de Fibonacci usando recursión de cola:

def fibonacci(n, a=0, b=1):
    if n == 0:
        return a
    return fibonacci(n-1, b, a+b)

La función toma tres parámetros:

  • \(n\): La posición del número deseado en la secuencia.
  • \(a\) y \(b\): Dos números que ayudan en el cálculo de la secuencia.

Aquí hay un desglose de cómo funciona la función:

  1. Caso Base: Si \(n\) es \(0\), la función devuelve \(a\). Este es el valor del \(n^th\) número en la secuencia.

  2. Caso Recursivo: Si \(n\) no es \(0\), la función se llama a sí misma con \(n-1\), \(b\), y \(a+b\). Estos parámetros cambian la posición en la secuencia y preparan los siguientes números para la suma.

Supongamos que queremos encontrar el \(5^th\) número en la secuencia de Fibonacci llamando a fibonacci(5).

Esto es lo que sucede:

  1. Paso 1: Dado que \(n = 5\), llama a fibonacci(4, 1, 1) (porque \(a = 0\), \(b = 1\), \(a + b = 1\)).
  2. Paso 2: Dado que \(n = 4\), llama a fibonacci(3, 1, 2) (porque \(a = 1\), \(b = 1\), \(a + b = 2\)).
  3. Paso 3: Dado que \(n = 3\), llama a fibonacci(2, 2, 3) (porque \(a = 1\), \(b = 2\), \(a + b = 3\)).
  4. Paso 4: Dado que \(n = 2\), llama a fibonacci(1, 3, 5) (porque \(a = 2\), \(b = 3\), \(a + b = 5\)).
  5. Paso 5: Dado que \(n = 1\), llama a fibonacci(0, 5, 8) (porque \(a = 3\), \(b = 5\), \(a + b = 8\)).
  6. Paso 6: Dado que \(n = 0\), devuelve \(a\), que es \(5\).

El resultado es \(5\), que es el \(5^th\) número en la secuencia de Fibonacci.

Aquí hay una representación visual de la pila de llamadas:

fibonacci(5, 0, 1)
  -> fibonacci(4, 1, 1)
    -> fibonacci(3, 1, 2)
      -> fibonacci(2, 2, 3)
        -> fibonacci(1, 3, 5)
          -> fibonacci(0, 5, 8)
            return 5

Ventajas y desventajas

La recursión tiene ciertas ventajas2:

  • Puede resultar en soluciones simples y elegantes para problemas que se dividen fácilmente en subproblemas.
  • Elimina la necesidad de control de bucles explícito.
  • Sigue la estructura matemática de una definición recursiva.

Las desventajas incluyen:

  • Puede ser menos eficiente (alto consumo de memoria) que la iteración debido a las llamadas repetidas y creación de marcos de pila.
  • Demasiada recursión puede desbordar la pila de llamadas y causar errores.
  • Puede ser más difícil de depurar y analizar que la iteración.

Por lo tanto, la recursión es una herramienta poderosa que debe usarse con discreción en los casos apropiados.


Recursión vs Iteración

La recursión y la iteración (usando ciclos) son paralelos y podemos usar cualquiera para resolver muchos problemas. Ambas técnicas tienen el potencial de resolver los mismos problemas, pero su implementación y eficiencia pueden variar. Tomemos el ejemplo del factorial:

Iterativo

def factorial_iterativo(n):
    resultado = 1
    for i in range(1, n+1):
        resultado *= i
    return resultado

Recursivo

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

La versión iterativa es más eficiente en términos de espacio, pero la recursiva es más limpia y fácil de entender. La elección entre recursión e iteración a menudo depende del problema específico, las restricciones de memoria y las preferencias del programador.


Conclusión

La recursión es una técnica clave que permite escribir algoritmos elegante, naturales y eficientes si se utiliza adecuadamente. Entender cómo dividir un problema en casos recursivos es esencial para dominar esta habilidad. La recursión ofrece una alternativa declarativa única para resolver problemas complejos sin necesidad de administrar bucles explícitos. Sin embargo, es crucial recordar siempre definir un caso base adecuado y ser consciente de las limitaciones de la recursión en términos de eficiencia y uso de memoria. Saber combinar recursión e iteración nos da flexibilidad al crear soluciones óptimas.

Como siempre, la clave está en encontrar el equilibrio adecuado y utilizar la herramienta correcta para el trabajo adecuado.



Referencias


  1. Kindler, E., & Krivy, I. (2011). Object-Oriented Simulation of systems with Java: A working introduction. BoD–Books on Demand. ↩︎

  2. Lutz, M. (2013). Learning Python: Powerful Object-Oriented Programming. O’Reilly Media, Incorporated. ↩︎