Python

Python

Tutorial Python: Módulo random

Aprende a usar el módulo random de Python para generar números aleatorios, seleccionar elementos y simular distribuciones probabilísticas.

Aprende Python y certifícate

Generación de números aleatorios

El módulo random de Python proporciona una serie de funciones para generar números aleatorios, lo que resulta extremadamente útil en diversas aplicaciones como simulaciones, juegos, pruebas y análisis estadístico. Este módulo utiliza el algoritmo Mersenne Twister, un generador de números pseudoaleatorios de alta calidad que produce secuencias de números que parecen aleatorios pero son deterministas si se conoce la semilla inicial.

Para empezar a utilizar el módulo random, primero debemos importarlo:

import random

Funciones básicas para generar números aleatorios

La función más simple del módulo es random(), que devuelve un número flotante aleatorio entre 0.0 (inclusive) y 1.0 (exclusive):

# Genera un número flotante aleatorio entre 0.0 y 1.0
valor = random.random()
print(valor)  # Ejemplo de salida: 0.7364926331879848

Si necesitamos un número flotante dentro de un rango específico, podemos usar la función uniform():

# Genera un número flotante aleatorio entre 10.5 y 20.5
valor = random.uniform(10.5, 20.5)
print(valor)  # Ejemplo de salida: 15.273876587431254

Para generar números enteros aleatorios, disponemos de la función randint(), que incluye ambos extremos del rango especificado:

# Genera un número entero aleatorio entre 1 y 6 (ambos inclusive)
dado = random.randint(1, 6)
print(f"Resultado del dado: {dado}")  # Ejemplo: Resultado del dado: 4

También existe la función randrange(), que permite especificar un rango con inicio, fin y paso, similar a la función range():

# Genera un número entero aleatorio entre 0 y 100, pero solo múltiplos de 5
valor = random.randrange(0, 101, 5)
print(valor)  # Posibles resultados: 0, 5, 10, 15, ..., 95, 100

Control de la aleatoriedad con semillas

A veces necesitamos que nuestros números "aleatorios" sean reproducibles, por ejemplo, para pruebas o depuración. Para esto, podemos establecer una semilla (seed) que inicializa el generador de números aleatorios:

# Establecer una semilla fija
random.seed(42)

# Ahora los números generados serán siempre los mismos
print(random.random())  # Siempre será: 0.6394267984578837
print(random.randint(1, 10))  # Siempre será: 1

# Si establecemos la misma semilla de nuevo, obtendremos la misma secuencia
random.seed(42)
print(random.random())  # De nuevo: 0.6394267984578837

Aplicaciones prácticas

Simulación de un juego de dados

Podemos simular fácilmente el lanzamiento de dados utilizando randint():

def lanzar_dados(cantidad=2, caras=6):
    """Simula el lanzamiento de varios dados."""
    return [random.randint(1, caras) for _ in range(cantidad)]

# Lanzar dos dados de 6 caras
resultado = lanzar_dados()
print(f"Resultado de los dados: {resultado}")
print(f"Suma total: {sum(resultado)}")

# Lanzar un dado de 20 caras (común en juegos de rol)
d20 = lanzar_dados(1, 20)[0]
print(f"Resultado del D20: {d20}")

Generación de contraseñas aleatorias

Podemos crear una función simple para generar contraseñas aleatorias:

def generar_password(longitud=12):
    """Genera una contraseña aleatoria con letras, números y símbolos."""
    caracteres = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+"
    password = ''.join(random.choice(caracteres) for _ in range(longitud))
    return password

# Generar una contraseña de 16 caracteres
nueva_password = generar_password(16)
print(f"Contraseña generada: {nueva_password}")

Simulación de probabilidades

Podemos usar números aleatorios para simular eventos con diferentes probabilidades:

def evento_con_probabilidad(probabilidad):
    """Retorna True con la probabilidad especificada (0-1)."""
    return random.random() < probabilidad

# Simular un evento con 30% de probabilidad
if evento_con_probabilidad(0.3):
    print("¡El evento ocurrió!")
else:
    print("El evento no ocurrió.")

# Contar cuántas veces ocurre un evento en múltiples intentos
exitos = sum(evento_con_probabilidad(0.3) for _ in range(1000))
print(f"El evento ocurrió {exitos} veces en 1000 intentos (~30% esperado)")

Generación de números aleatorios con distribuciones específicas

Además de la distribución uniforme (todos los valores tienen la misma probabilidad), el módulo random ofrece funciones para generar números con otras distribuciones:

# Distribución normal (gaussiana) con media 0 y desviación estándar 1
valor_normal = random.normalvariate(0, 1)
print(f"Valor de distribución normal: {valor_normal}")

# Distribución triangular entre min, max con moda
valor_triangular = random.triangular(0, 10, 5)
print(f"Valor de distribución triangular: {valor_triangular}")

Generación de bytes aleatorios

Para aplicaciones criptográficas o que requieran datos binarios aleatorios, podemos usar:

# Generar 8 bytes aleatorios
bytes_aleatorios = random.randbytes(8)
print(f"Bytes aleatorios: {bytes_aleatorios}")
print(f"Representación hexadecimal: {bytes_aleatorios.hex()}")

Consideraciones de seguridad

Es importante destacar que el módulo random no es adecuado para aplicaciones criptográficas o de seguridad. Para estos casos, Python proporciona el módulo secrets que ofrece una fuente de aleatoriedad criptográficamente segura:

import secrets

# Generar un token seguro de 16 bytes
token = secrets.token_hex(16)
print(f"Token seguro: {token}")

# Generar un número entero seguro en un rango
num_seguro = secrets.randbelow(1000)  # Número entre 0 y 999
print(f"Número aleatorio seguro: {num_seguro}")

El módulo random es perfecto para simulaciones, juegos y aplicaciones donde la predictibilidad no es un problema de seguridad, mientras que secrets debe usarse cuando la seguridad es crítica, como en la generación de tokens, contraseñas o claves.

Selección aleatoria de elementos

El módulo random de Python no solo permite generar números aleatorios, sino que también ofrece funciones especializadas para seleccionar elementos de forma aleatoria a partir de secuencias como listas, tuplas o cadenas. Esta funcionalidad es extremadamente útil en aplicaciones como juegos, simulaciones, muestreo estadístico o cualquier situación donde necesitemos introducir aleatoriedad en la selección de datos.

Selección de un único elemento

La función más básica para seleccionar elementos aleatorios es choice(), que selecciona un elemento al azar de una secuencia:

import random

# Seleccionar una carta aleatoria de una baraja
palos = ["♠", "♥", "♦", "♣"]
valores = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]

palo = random.choice(palos)
valor = random.choice(valores)
print(f"Carta seleccionada: {valor}{palo}")  # Ejemplo: 7♥

Esta función es perfecta para simular situaciones donde se extrae un elemento al azar, como sacar una carta de una baraja o elegir un ganador de una rifa.

Selección de múltiples elementos

Cuando necesitamos seleccionar varios elementos de una secuencia, podemos usar la función choices(), que permite seleccionar elementos con reemplazo (el mismo elemento puede ser seleccionado múltiples veces):

# Simular 5 lanzamientos de un dado
resultados = random.choices([1, 2, 3, 4, 5, 6], k=5)
print(f"Resultados de los dados: {resultados}")  # Ejemplo: [3, 6, 2, 6, 1]

# Seleccionar 3 cartas con reemplazo (como sacar y volver a meter)
cartas = [f"{v}{p}" for v in valores for p in palos]
mano = random.choices(cartas, k=3)
print(f"Cartas seleccionadas: {mano}")  # Ejemplo: ['4♠', '10♥', '4♠']

La función choices() también permite especificar pesos para cada elemento, lo que resulta útil cuando queremos que algunos elementos tengan mayor probabilidad de ser seleccionados:

# Simular una ruleta con diferentes probabilidades
opciones = ["rojo", "negro", "verde"]
# El verde (0) tiene menos probabilidad que rojo y negro
pesos = [18, 18, 2]  # Corresponde a una ruleta con 38 casillas

resultados = random.choices(opciones, weights=pesos, k=10)
print(f"Resultados de la ruleta: {resultados}")

# Contar frecuencias
frecuencias = {opcion: resultados.count(opcion) for opcion in opciones}
print(f"Frecuencias: {frecuencias}")

Muestreo sin reemplazo

Para seleccionar elementos sin repetición (como sacar cartas de una baraja sin devolverlas), usamos la función sample():

# Crear una baraja de cartas
baraja = [f"{v}{p}" for v in valores for p in palos]

# Repartir una mano de póker (5 cartas sin repetición)
mano_poker = random.sample(baraja, k=5)
print(f"Mano de póker: {mano_poker}")  # Ejemplo: ['A♠', '10♦', '3♣', 'K♥', '7♠']

# Seleccionar 3 ganadores de un sorteo
participantes = ["Ana", "Carlos", "Elena", "David", "Beatriz", "Fernando"]
ganadores = random.sample(participantes, k=3)
print(f"Los ganadores son: {ganadores}")  # Ejemplo: ['Elena', 'Fernando', 'Ana']

Es importante recordar que k no puede ser mayor que la longitud de la secuencia cuando usamos sample(), ya que no podemos seleccionar más elementos únicos de los que existen.

Selección aleatoria de caracteres

Podemos aplicar estas funciones a cadenas de texto para seleccionar caracteres aleatorios:

# Seleccionar un carácter aleatorio de una cadena
texto = "Python es divertido"
caracter = random.choice(texto)
print(f"Carácter aleatorio: '{caracter}'")  # Ejemplo: 'o'

# Seleccionar 5 caracteres aleatorios sin repetición
caracteres = random.sample(texto, k=5)
print(f"Caracteres seleccionados: {caracteres}")  # Ejemplo: ['P', 'e', 'i', 'd', 'n']

Aplicaciones prácticas

Generador de contraseñas mejorado

Podemos crear un generador de contraseñas más sofisticado que asegure la presencia de diferentes tipos de caracteres:

def generar_password_segura(longitud=12):
    """Genera una contraseña aleatoria con al menos un carácter de cada tipo."""
    minusculas = "abcdefghijklmnopqrstuvwxyz"
    mayusculas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    numeros = "0123456789"
    simbolos = "!@#$%^&*()-_=+[]{}|;:,.<>?"
    
    # Asegurar al menos un carácter de cada tipo
    password = [
        random.choice(minusculas),
        random.choice(mayusculas),
        random.choice(numeros),
        random.choice(simbolos)
    ]
    
    # Completar con caracteres aleatorios
    todos_caracteres = minusculas + mayusculas + numeros + simbolos
    password.extend(random.choices(todos_caracteres, k=longitud-4))
    
    # Mezclar la contraseña para que no siga un patrón predecible
    random.shuffle(password)
    
    return ''.join(password)

# Generar una contraseña segura
password = generar_password_segura(16)
print(f"Contraseña segura: {password}")

Selección de preguntas para un examen

Podemos simular la selección aleatoria de preguntas para un examen:

def crear_examen(banco_preguntas, num_preguntas=10, num_faciles=6, num_dificiles=4):
    """Crea un examen con preguntas fáciles y difíciles seleccionadas aleatoriamente."""
    preguntas_faciles = random.sample(banco_preguntas['faciles'], k=num_faciles)
    preguntas_dificiles = random.sample(banco_preguntas['dificiles'], k=num_dificiles)
    
    examen = preguntas_faciles + preguntas_dificiles
    # Mezclar las preguntas para que no estén agrupadas por dificultad
    random.shuffle(examen)
    
    return examen

# Banco de preguntas ejemplo
banco = {
    'faciles': [f"Pregunta fácil #{i}" for i in range(1, 21)],
    'dificiles': [f"Pregunta difícil #{i}" for i in range(1, 16)]
}

# Crear un examen
examen = crear_examen(banco)
print("Examen generado:")
for i, pregunta in enumerate(examen, 1):
    print(f"{i}. {pregunta}")

Simulación de una lotería

Podemos simular un sorteo de lotería donde se extraen bolas numeradas sin reemplazo:

def sorteo_loteria(rango_numeros=(1, 49), bolas_extraidas=6):
    """Simula un sorteo de lotería extrayendo bolas numeradas sin reemplazo."""
    numeros_disponibles = list(range(rango_numeros[0], rango_numeros[1] + 1))
    return sorted(random.sample(numeros_disponibles, k=bolas_extraidas))

# Simular un sorteo de lotería
combinacion_ganadora = sorteo_loteria()
print(f"Combinación ganadora: {combinacion_ganadora}")

# Simular múltiples apuestas
num_apuestas = 5
print("\nTus apuestas:")
for i in range(num_apuestas):
    apuesta = sorteo_loteria()
    print(f"Apuesta {i+1}: {apuesta}")

Selección aleatoria ponderada personalizada

A veces necesitamos implementar selecciones aleatorias con probabilidades específicas que no se ajustan directamente a choices(). Por ejemplo, para simular un sistema de "gacha" en un juego:

def seleccion_gacha():
    """Simula un sistema de gacha con diferentes raridades de objetos."""
    raridades = {
        "común": 0.70,      # 70% de probabilidad
        "raro": 0.25,       # 25% de probabilidad
        "épico": 0.04,      # 4% de probabilidad
        "legendario": 0.01  # 1% de probabilidad
    }
    
    # Objetos por rareza
    objetos = {
        "común": ["Poción", "Piedra", "Madera", "Cuero", "Hierro"],
        "raro": ["Espada", "Escudo", "Arco", "Armadura", "Amuleto"],
        "épico": ["Varita mágica", "Grimorio", "Cetro", "Corona", "Capa"],
        "legendario": ["Excalibur", "Santo Grial", "Anillo único", "Mjölnir"]
    }
    
    # Seleccionar rareza según probabilidades
    rareza = random.choices(
        list(raridades.keys()),
        weights=list(raridades.values()),
        k=1
    )[0]
    
    # Seleccionar objeto de esa rareza
    objeto = random.choice(objetos[rareza])
    
    return rareza, objeto

# Simular 10 tiradas
print("Resultados del gacha:")
for i in range(10):
    rareza, objeto = seleccion_gacha()
    print(f"Tirada {i+1}: {objeto} ({rareza})")

Las funciones de selección aleatoria del módulo random son herramientas versátiles que pueden aplicarse en numerosos contextos, desde juegos y simulaciones hasta aplicaciones educativas y científicas. La capacidad de seleccionar elementos con o sin reemplazo, y con diferentes probabilidades, nos permite modelar una amplia variedad de situaciones del mundo real donde la aleatoriedad juega un papel importante.

Mezcla de secuencias

El módulo random de Python ofrece una funcionalidad especialmente útil para reordenar elementos de forma aleatoria mediante la función shuffle(). Esta capacidad resulta fundamental en numerosas aplicaciones donde necesitamos alterar el orden de los elementos de una colección de manera impredecible.

A diferencia de las funciones de selección que hemos visto anteriormente, shuffle() modifica la secuencia original in-place, es decir, altera directamente la colección sin crear una nueva:

import random

# Crear una lista ordenada
cartas = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]

# Mezclar la lista
random.shuffle(cartas)

print(f"Cartas mezcladas: {cartas}")  # Ejemplo: ['7', 'A', 'J', '4', '2', '10', 'K', '5', '8', 'Q', '3', '9', '6']

Es importante destacar que shuffle() solo funciona con objetos mutables como listas. No podemos aplicarla directamente a tuplas, cadenas u otros objetos inmutables:

# Esto generará un error
palabra = "Python"
# random.shuffle(palabra)  # TypeError: 'str' object does not support item assignment

# Para mezclar una cadena, primero convertimos a lista, mezclamos y luego volvemos a unir
lista_caracteres = list(palabra)
random.shuffle(lista_caracteres)
palabra_mezclada = ''.join(lista_caracteres)
print(f"Palabra mezclada: {palabra_mezclada}")  # Ejemplo: "yPntho"

Implementación de barajas de cartas

Una de las aplicaciones más intuitivas de shuffle() es la simulación de barajas de cartas:

def crear_baraja():
    """Crea una baraja de cartas ordenada."""
    palos = ["♠", "♥", "♦", "♣"]
    valores = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
    return [f"{v}{p}" for p in palos for v in valores]

def barajar(mazo):
    """Mezcla un mazo de cartas."""
    random.shuffle(mazo)
    return mazo  # Aunque no es necesario retornar el mazo, puede ser útil para encadenar operaciones

# Crear y barajar una baraja
baraja = crear_baraja()
print(f"Baraja ordenada (primeras 5 cartas): {baraja[:5]}")

barajar(baraja)
print(f"Baraja mezclada (primeras 5 cartas): {baraja[:5]}")

Juegos de mesa y simulaciones

La mezcla aleatoria es esencial en la simulación de juegos de mesa donde el orden de los elementos debe ser impredecible:

def juego_memoria():
    """Simula la preparación de un juego de memoria con parejas de cartas."""
    # Crear parejas de símbolos
    simbolos = ["🍎", "🍌", "🍒", "🍓", "🍇", "🍉", "🍋", "🍍"]
    parejas = simbolos * 2
    
    # Mezclar las cartas
    random.shuffle(parejas)
    
    # Disponer en un tablero 4x4
    tablero = []
    for i in range(0, 16, 4):
        fila = parejas[i:i+4]
        tablero.append(fila)
    
    return tablero

# Crear un juego de memoria
tablero = juego_memoria()
print("Tablero de memoria (cartas boca abajo):")
for fila in tablero:
    print(fila)

Generación de cuestionarios aleatorios

En aplicaciones educativas, podemos usar shuffle() para crear cuestionarios donde tanto las preguntas como las opciones de respuesta aparecen en orden aleatorio:

def crear_cuestionario(preguntas, mezclar_opciones=True):
    """Crea un cuestionario con preguntas en orden aleatorio."""
    # Hacer una copia para no modificar el original
    cuestionario = preguntas.copy()
    
    # Mezclar el orden de las preguntas
    random.shuffle(cuestionario)
    
    # Opcionalmente mezclar las opciones de cada pregunta
    if mezclar_opciones:
        for pregunta in cuestionario:
            # Guardar la respuesta correcta
            respuesta_correcta = pregunta["opciones"][pregunta["correcta"]]
            
            # Mezclar las opciones
            random.shuffle(pregunta["opciones"])
            
            # Actualizar el índice de la respuesta correcta
            pregunta["correcta"] = pregunta["opciones"].index(respuesta_correcta)
    
    return cuestionario

# Ejemplo de preguntas
preguntas = [
    {
        "texto": "¿Cuál es la capital de Francia?",
        "opciones": ["París", "Londres", "Madrid", "Berlín"],
        "correcta": 0
    },
    {
        "texto": "¿En qué año se fundó Python?",
        "opciones": ["1991", "1989", "2000", "1995"],
        "correcta": 0
    },
    {
        "texto": "¿Quién escribió 'Don Quijote de la Mancha'?",
        "opciones": ["Miguel de Cervantes", "Federico García Lorca", "Gabriel García Márquez", "Pablo Neruda"],
        "correcta": 0
    }
]

# Crear un cuestionario aleatorio
cuestionario = crear_cuestionario(preguntas)

# Mostrar el cuestionario
print("CUESTIONARIO:")
for i, pregunta in enumerate(cuestionario, 1):
    print(f"\nPregunta {i}: {pregunta['texto']}")
    for j, opcion in enumerate(pregunta["opciones"]):
        print(f"  {j+1}. {opcion}")
    print(f"  (Respuesta correcta: {pregunta['correcta']+1})")

Algoritmo de Fisher-Yates

Internamente, shuffle() utiliza una variante del algoritmo de Fisher-Yates (también conocido como algoritmo de Knuth), que es un método eficiente para generar una permutación aleatoria de una secuencia finita. Podemos implementar nuestra propia versión de este algoritmo para entender cómo funciona:

def fisher_yates_shuffle(lista):
    """Implementación del algoritmo de Fisher-Yates para mezclar una lista."""
    n = len(lista)
    for i in range(n-1, 0, -1):
        # Elegir un índice aleatorio entre 0 e i (inclusive)
        j = random.randint(0, i)
        # Intercambiar elementos
        lista[i], lista[j] = lista[j], lista[i]
    return lista

# Probar nuestro algoritmo
numeros = list(range(1, 11))
print(f"Lista original: {numeros}")

fisher_yates_shuffle(numeros)
print(f"Lista mezclada: {numeros}")

Este algoritmo tiene una complejidad de tiempo O(n), lo que lo hace muy eficiente incluso para secuencias grandes.

Generación de datos aleatorios para pruebas

La mezcla de secuencias es especialmente útil para generar datos de prueba en desarrollo de software:

def generar_datos_prueba(n=100):
    """Genera un conjunto de datos de prueba con valores mezclados."""
    # Crear datos de ejemplo
    nombres = ["Ana", "Carlos", "Elena", "David", "Beatriz", "Fernando", "Gabriela", "Héctor"]
    apellidos = ["García", "López", "Martínez", "Rodríguez", "Fernández", "González", "Pérez", "Sánchez"]
    ciudades = ["Madrid", "Barcelona", "Valencia", "Sevilla", "Bilbao", "Málaga", "Zaragoza", "Murcia"]
    
    # Generar datos
    datos = []
    for _ in range(n):
        # Mezclar las listas para seleccionar elementos aleatorios
        random.shuffle(nombres)
        random.shuffle(apellidos)
        random.shuffle(ciudades)
        
        # Crear un registro
        registro = {
            "id": _ + 1,
            "nombre": nombres[0],
            "apellido": apellidos[0],
            "edad": random.randint(18, 80),
            "ciudad": ciudades[0]
        }
        datos.append(registro)
    
    return datos

# Generar 5 registros de prueba
datos_prueba = generar_datos_prueba(5)
for registro in datos_prueba:
    print(registro)

Simulación de reparto de cartas

Podemos combinar shuffle() con otras operaciones para simular juegos de cartas más complejos:

def repartir_cartas(num_jugadores, cartas_por_jugador):
    """Reparte cartas a varios jugadores."""
    # Crear y mezclar la baraja
    baraja = crear_baraja()
    random.shuffle(baraja)
    
    # Repartir cartas
    manos = []
    for i in range(num_jugadores):
        inicio = i * cartas_por_jugador
        fin = inicio + cartas_por_jugador
        mano = baraja[inicio:fin]
        manos.append(mano)
    
    # Devolver las manos y el resto de la baraja
    resto_baraja = baraja[num_jugadores * cartas_por_jugador:]
    return manos, resto_baraja

# Simular una partida de póker (5 jugadores, 2 cartas cada uno)
manos_jugadores, resto = repartir_cartas(5, 2)

print("Reparto de cartas para póker Texas Hold'em:")
for i, mano in enumerate(manos_jugadores, 1):
    print(f"Jugador {i}: {mano}")

# Mostrar las cartas comunitarias (flop, turn y river)
print(f"\nFlop: {resto[:3]}")
print(f"Turn: {resto[3]}")
print(f"River: {resto[4]}")

Generación de playlists aleatorias

Otra aplicación común es la creación de listas de reproducción aleatorias:

def crear_playlist_aleatoria(canciones, duracion_maxima=60):
    """Crea una playlist aleatoria con una duración máxima en minutos."""
    # Hacer una copia y mezclar
    playlist = canciones.copy()
    random.shuffle(playlist)
    
    # Seleccionar canciones hasta alcanzar la duración máxima
    seleccionadas = []
    duracion_total = 0
    
    for cancion in playlist:
        if duracion_total + cancion["duracion"] <= duracion_maxima:
            seleccionadas.append(cancion)
            duracion_total += cancion["duracion"]
    
    return seleccionadas, duracion_total

# Lista de canciones de ejemplo
canciones = [
    {"titulo": "Bohemian Rhapsody", "artista": "Queen", "duracion": 5.9},
    {"titulo": "Imagine", "artista": "John Lennon", "duracion": 3.1},
    {"titulo": "Hotel California", "artista": "Eagles", "duracion": 6.5},
    {"titulo": "Sweet Child O' Mine", "artista": "Guns N' Roses", "duracion": 5.6},
    {"titulo": "Billie Jean", "artista": "Michael Jackson", "duracion": 4.9},
    {"titulo": "Stairway to Heaven", "artista": "Led Zeppelin", "duracion": 8.0},
    {"titulo": "Yesterday", "artista": "The Beatles", "duracion": 2.1},
    {"titulo": "Smells Like Teen Spirit", "artista": "Nirvana", "duracion": 5.0},
    {"titulo": "Nothing Else Matters", "artista": "Metallica", "duracion": 6.3},
    {"titulo": "Wonderwall", "artista": "Oasis", "duracion": 4.2}
]

# Crear una playlist de 30 minutos
playlist, duracion = crear_playlist_aleatoria(canciones, 30)

print(f"Playlist aleatoria (Duración total: {duracion:.1f} minutos):")
for i, cancion in enumerate(playlist, 1):
    print(f"{i}. {cancion['titulo']} - {cancion['artista']} ({cancion['duracion']} min)")

Consideraciones sobre la aleatoriedad

Es importante recordar que la calidad de la mezcla depende del generador de números aleatorios subyacente. Para aplicaciones críticas donde la imprevisibilidad es esencial (como juegos de azar en línea), puede ser necesario utilizar fuentes de aleatoriedad más robustas:

import secrets

def shuffle_seguro(lista):
    """Versión más segura de shuffle usando el módulo secrets."""
    n = len(lista)
    for i in range(n-1, 0, -1):
        # Usar secrets para mayor seguridad criptográfica
        j = secrets.randbelow(i + 1)
        lista[i], lista[j] = lista[j], lista[i]
    return lista

# Ejemplo de uso para una aplicación de seguridad crítica
claves_cifrado = ["clave1", "clave2", "clave3", "clave4", "clave5"]
shuffle_seguro(claves_cifrado)
print(f"Claves mezcladas de forma segura: {claves_cifrado}")

La función shuffle() del módulo random es una herramienta versátil que simplifica enormemente la tarea de reordenar elementos de forma aleatoria, siendo fundamental en juegos, simulaciones y generación de datos de prueba. Su implementación eficiente y fácil uso la convierten en un componente esencial del arsenal de cualquier programador Python.

Distribuciones probabilísticas

El módulo random de Python no solo permite generar números aleatorios uniformes o seleccionar elementos al azar, sino que también ofrece funciones para generar valores aleatorios que siguen diferentes distribuciones probabilísticas. Estas distribuciones son fundamentales en estadística, simulaciones científicas, análisis de datos y modelado de fenómenos naturales.

A diferencia de la distribución uniforme (donde todos los valores tienen la misma probabilidad de ocurrir), las distribuciones probabilísticas permiten modelar situaciones donde ciertos valores son más probables que otros, siguiendo patrones matemáticos específicos.

import random
import matplotlib.pyplot as plt

Distribución normal (gaussiana)

La distribución normal o gaussiana es quizás la más utilizada en estadística y ciencias. Describe fenómenos naturales como la altura de una población, errores de medición o fluctuaciones aleatorias.

# Generar un valor aleatorio con distribución normal
# Parámetros: media (mu) y desviación estándar (sigma)
valor = random.normalvariate(mu=0, sigma=1)
print(f"Valor aleatorio con distribución normal: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_normal = [random.normalvariate(mu=100, sigma=15) for _ in range(1000)]

La función normalvariate() genera valores aleatorios donde la mayoría se concentran cerca de la media (parámetro mu), y la desviación estándar (parámetro sigma) controla qué tan dispersos están los valores.

También existe la variante gauss(), que implementa el mismo concepto pero con un algoritmo ligeramente diferente:

# Alternativa a normalvariate
valor_gauss = random.gauss(mu=0, sigma=1)

Distribución triangular

La distribución triangular es útil cuando conocemos los valores mínimo, máximo y más probable (moda). Es común en análisis de riesgos y estimaciones de proyectos.

# Generar un valor con distribución triangular
# Parámetros: mínimo, máximo y moda (valor más probable)
valor = random.triangular(low=0, high=10, mode=5)
print(f"Valor aleatorio con distribución triangular: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_triangular = [random.triangular(0, 100, 60) for _ in range(1000)]

Esta distribución es particularmente útil cuando tenemos estimaciones de "mejor caso", "peor caso" y "caso más probable", como en la planificación de proyectos.

Distribución exponencial

La distribución exponencial modela el tiempo entre eventos que ocurren a una tasa constante e independiente, como las llegadas de clientes a un establecimiento o el tiempo hasta que un componente falla.

# Generar un valor con distribución exponencial
# El parámetro lambda controla la tasa (mayor lambda = menor media)
valor = random.expovariate(lambd=0.5)
print(f"Valor aleatorio con distribución exponencial: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_exponencial = [random.expovariate(0.1) for _ in range(1000)]

El parámetro lambd es la tasa de ocurrencia (lambda). La media de esta distribución es 1/lambda.

Distribución beta

La distribución beta es versátil para modelar proporciones o probabilidades, ya que genera valores entre 0 y 1. Es útil en análisis bayesiano y para representar incertidumbre sobre probabilidades.

# Generar un valor con distribución beta
# Parámetros alpha y beta controlan la forma
valor = random.betavariate(alpha=2, beta=5)
print(f"Valor aleatorio con distribución beta: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_beta = [random.betavariate(2, 5) for _ in range(1000)]

Los parámetros alpha y beta controlan la forma de la distribución. Cuando ambos son mayores que 1, la distribución tiene forma de campana; cuando ambos son menores que 1, tiene forma de U.

Distribución gamma

La distribución gamma modela el tiempo hasta que ocurren k eventos en un proceso de Poisson. Es útil en teoría de colas, análisis de supervivencia y modelado de precipitaciones.

# Generar un valor con distribución gamma
# Parámetros: alpha (forma) y beta (escala)
valor = random.gammavariate(alpha=2, beta=1)
print(f"Valor aleatorio con distribución gamma: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_gamma = [random.gammavariate(2, 1) for _ in range(1000)]

El parámetro alpha controla la forma y beta controla la escala de la distribución.

Distribución log-normal

La distribución log-normal aparece cuando el logaritmo de una variable sigue una distribución normal. Es común en fenómenos como ingresos, precios de acciones o tamaños de partículas.

# Generar un valor con distribución log-normal
# Parámetros: media y desviación estándar del logaritmo
valor = random.lognormvariate(mu=0, sigma=0.5)
print(f"Valor aleatorio con distribución log-normal: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_lognormal = [random.lognormvariate(0, 0.5) for _ in range(1000)]

Esta distribución genera valores positivos con una cola larga hacia la derecha, lo que la hace adecuada para modelar variables que no pueden ser negativas y tienen valores extremos ocasionales.

Distribución de Pareto

La distribución de Pareto sigue el principio de "los pocos vitales y los muchos triviales" (regla 80/20). Es útil para modelar distribuciones de riqueza, tamaños de ciudades o frecuencia de palabras.

# Generar un valor con distribución de Pareto
# El parámetro alpha controla la forma (menor alpha = cola más larga)
valor = random.paretovariate(alpha=1.5)
print(f"Valor aleatorio con distribución de Pareto: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_pareto = [random.paretovariate(1.5) for _ in range(1000)]

Un valor menor de alpha produce una distribución con una cola más larga, lo que significa mayor probabilidad de valores extremos.

Distribución de von Mises

La distribución de von Mises es la equivalente circular de la distribución normal. Es útil para ángulos, direcciones o datos cíclicos como horas del día o estaciones.

# Generar un valor con distribución de von Mises
# Parámetros: mu (dirección media) y kappa (concentración)
valor = random.vonmisesvariate(mu=0, kappa=4)
print(f"Valor aleatorio con distribución de von Mises: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_vonmises = [random.vonmisesvariate(0, 4) for _ in range(1000)]

El parámetro mu indica la dirección media (en radianes) y kappa controla la concentración alrededor de esa dirección (mayor kappa = distribución más concentrada).

Distribución de Weibull

La distribución de Weibull es ampliamente utilizada en análisis de fiabilidad y supervivencia. Modela el tiempo hasta el fallo de componentes y sistemas.

# Generar un valor con distribución de Weibull
# Parámetros: alpha (escala) y beta (forma)
valor = random.weibullvariate(alpha=1, beta=1.5)
print(f"Valor aleatorio con distribución de Weibull: {valor:.4f}")

# Generar una muestra de 1000 valores
muestra_weibull = [random.weibullvariate(1, 1.5) for _ in range(1000)]

El parámetro alpha controla la escala y beta la forma. Cuando beta = 1, la distribución de Weibull se reduce a la distribución exponencial.

Aplicaciones prácticas

Simulación de tiempos de espera

Podemos simular los tiempos de espera entre llegadas de clientes a un establecimiento:

def simular_llegadas_clientes(tiempo_total=60, tasa_media=5):
    """
    Simula llegadas de clientes durante un período de tiempo.
    
    Args:
        tiempo_total: Duración de la simulación en minutos
        tasa_media: Número medio de clientes por minuto
    
    Returns:
        Lista de tiempos de llegada
    """
    tiempos = []
    tiempo_actual = 0
    
    while tiempo_actual < tiempo_total:
        # Tiempo hasta la próxima llegada (distribución exponencial)
        tiempo_hasta_siguiente = random.expovariate(tasa_media)
        tiempo_actual += tiempo_hasta_siguiente
        
        if tiempo_actual < tiempo_total:
            tiempos.append(tiempo_actual)
    
    return tiempos

# Simular llegadas durante una hora
llegadas = simular_llegadas_clientes(60, 2)
print(f"Número de clientes en una hora: {len(llegadas)}")
print(f"Primeras 5 llegadas (minutos): {[round(t, 2) for t in llegadas[:5]]}")

Simulación de alturas de una población

Podemos generar una muestra de alturas de una población utilizando la distribución normal:

def generar_alturas(n=1000, genero="mixto"):
    """
    Genera alturas aleatorias para una población.
    
    Args:
        n: Número de personas
        genero: "hombre", "mujer" o "mixto"
    
    Returns:
        Lista de alturas en centímetros
    """
    if genero == "hombre":
        # Media y desviación estándar para hombres (en cm)
        return [random.normalvariate(176, 7) for _ in range(n)]
    elif genero == "mujer":
        # Media y desviación estándar para mujeres (en cm)
        return [random.normalvariate(163, 6) for _ in range(n)]
    else:
        # Población mixta (50% hombres, 50% mujeres)
        hombres = generar_alturas(n // 2, "hombre")
        mujeres = generar_alturas(n - (n // 2), "mujer")
        return hombres + mujeres

# Generar alturas para 1000 personas
alturas = generar_alturas(1000)
print(f"Altura media de la muestra: {sum(alturas)/len(alturas):.2f} cm")
print(f"Altura mínima: {min(alturas):.2f} cm, Altura máxima: {max(alturas):.2f} cm")

Simulación de duración de baterías

Podemos usar la distribución de Weibull para simular la vida útil de baterías:

def simular_duracion_baterias(n=100, vida_media=1000, forma=2.5):
    """
    Simula la duración de baterías en horas.
    
    Args:
        n: Número de baterías
        vida_media: Vida media esperada en horas
        forma: Parámetro de forma (beta) de Weibull
    
    Returns:
        Lista de duraciones en horas
    """
    # Calcular el parámetro de escala a partir de la vida media y forma
    # Para Weibull: E[X] = alpha * Gamma(1 + 1/beta)
    # Para beta=2.5, Gamma(1+1/2.5) ≈ 0.8873
    escala = vida_media / 0.8873
    
    return [random.weibullvariate(escala, forma) for _ in range(n)]

# Simular la duración de 100 baterías
duraciones = simular_duracion_baterias(100)
print(f"Duración media: {sum(duraciones)/len(duraciones):.2f} horas")
print(f"Porcentaje de baterías que duran menos de 800 horas: {sum(1 for d in duraciones if d < 800)/len(duraciones)*100:.1f}%")

Simulación de rendimientos financieros

La distribución log-normal es adecuada para simular rendimientos de inversiones:

def simular_rendimiento_inversion(capital_inicial=1000, rendimiento_anual=0.08, 
                                 volatilidad=0.15, años=10, simulaciones=100):
    """
    Simula el rendimiento de una inversión utilizando un modelo log-normal.
    
    Args:
        capital_inicial: Capital inicial en euros
        rendimiento_anual: Rendimiento anual esperado (0.08 = 8%)
        volatilidad: Volatilidad anual (0.15 = 15%)
        años: Número de años
        simulaciones: Número de simulaciones
    
    Returns:
        Lista de capitales finales
    """
    resultados = []
    
    for _ in range(simulaciones):
        capital = capital_inicial
        
        for _ in range(años):
            # Rendimiento anual con distribución log-normal
            # Ajustamos mu para que la media sea el rendimiento esperado
            mu = rendimiento_anual - (volatilidad**2) / 2
            rendimiento = random.lognormvariate(mu, volatilidad)
            capital *= (1 + rendimiento)
        
        resultados.append(capital)
    
    return resultados

# Simular 100 escenarios de inversión
resultados = simular_rendimiento_inversion()
print(f"Capital medio después de 10 años: {sum(resultados)/len(resultados):.2f} €")
print(f"Mejor escenario: {max(resultados):.2f} €")
print(f"Peor escenario: {min(resultados):.2f} €")

Simulación de lluvia diaria

Podemos usar la distribución gamma para simular precipitaciones:

def simular_lluvia_mensual(dias=30, probabilidad_lluvia=0.3, 
                          forma=2, escala=5):
    """
    Simula la lluvia diaria durante un mes.
    
    Args:
        dias: Número de días
        probabilidad_lluvia: Probabilidad de que llueva en un día
        forma: Parámetro de forma de la distribución gamma
        escala: Parámetro de escala de la distribución gamma
    
    Returns:
        Lista de precipitaciones diarias en mm
    """
    precipitaciones = []
    
    for _ in range(dias):
        # Determinar si llueve ese día
        if random.random() < probabilidad_lluvia:
            # Cantidad de lluvia (distribución gamma)
            cantidad = random.gammavariate(forma, escala)
            precipitaciones.append(cantidad)
        else:
            precipitaciones.append(0)
    
    return precipitaciones

# Simular la lluvia de un mes
lluvia = simular_lluvia_mensual()
print(f"Días con lluvia: {sum(1 for p in lluvia if p > 0)}")
print(f"Precipitación total: {sum(lluvia):.1f} mm")
print(f"Día más lluvioso: {max(lluvia):.1f} mm")

Las distribuciones probabilísticas del módulo random nos permiten modelar una amplia variedad de fenómenos naturales y procesos estocásticos. Al elegir la distribución adecuada para cada situación, podemos crear simulaciones más realistas y obtener resultados que reflejen mejor el comportamiento del mundo real.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Módulo random

Evalúa tus conocimientos de esta lección Módulo random con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender cómo generar números aleatorios básicos y controlar la aleatoriedad con semillas.
  • Aprender a seleccionar elementos aleatorios de secuencias con y sin reemplazo, incluyendo selecciones ponderadas.
  • Saber mezclar secuencias de forma aleatoria y aplicar esta técnica en simulaciones y juegos.
  • Conocer las principales distribuciones probabilísticas disponibles en el módulo random y su aplicación práctica.
  • Aplicar funciones del módulo random en ejemplos reales como simulaciones, generación de contraseñas y modelado estadístico.