01 · Punto de partida
Sortear no es señalar con el dedo
El Tema 22 te dio enteros parejos. Ahora esos enteros son posiciones: el ganador de una rifa, la carta que sale, la canción que suena. La regla no cambió (prob. 1/n por elemento), pero aparecen trampas nuevas: listas que se modifican entre sorteos, índices con % viciado y “el primero nunca sale” por off-by-one.
La receta es una sola línea —lista[floor(u·n)]— y dos decisiones: con o sin reemplazo (este tema es con reemplazo: el elegido puede volver a salir) y simulación o secreto.
- ¿Por qué
lista[int(u·n)]es parejo ylista[x % n]a veces no? - ¿Qué significa elegir con reemplazo?
- ¿Cuándo
choicey cuándosecrets.choice? - ¿Por qué no conviene mezclar para sacar un solo elemento?
02 · Definición
Índice parejo, elemento parejo
Índice parejo
i = floor(u·n) en 0 … n−1: hereda el 1/n del Tema 22. El elemento es lista[i], sin tocar la lista.
Con reemplazo
Cada sorteo parte de la lista completa: el mismo puede salir dos veces seguidas. Sin reemplazo es muestreo (Tema 25).
Ponderada ≠ pareja
Si unos pesan más (loot raro), ya no es 1/n: es choices(pesos). Este tema es equiprobable; la ponderada se menciona y se deriva.
| Caso | Receta | Notas |
|---|---|---|
| Un elemento, simulación | random.choice(lista) | Índice floor interno, con reemplazo |
| Un elemento, con premio | secrets.choice(lista) | Sin semilla, sin sesgo (Tema 18) |
| Varios con reemplazo | choices(lista, k=r) | Pueden repetirse; no es muestra (Tema 25) |
03 · Técnica correcta
Floor sí, módulo a veces, mezcla no
choice hace floor(u·n) por dentro: nunca toca la lista y cuesta O(1). Las alternativas tienen costo o sesgo escondido.
- Floor manual
lista[math.floor(rng.random()*len(lista))]: idéntico achoice. Útil para entender y para fuentes propias con rechazo (Tema 22).- Módulo heredado
lista[x % n]con fuente0 … m−1sesga sim % n ≠ 0: los primerosm % nelementos salen de más. Con m = 100 y n = 6, cuatro elementos tienen ventaja.- Mezclar para uno
shuffle+ tomar el primero cuesta O(n) y muta la lista: despilfarro para un solo sorteo. La mezcla es del Tema 24.
Bien sorteado
choice / floor
O(1), sin mutar, parejo 1/n. Repetible con semilla para depurar, auditable contando frecuencias.
Mal sorteado
% sobre resto o shuffle
Sesgo sistemático o costo O(n) con efectos laterales. El sorteo “sale” pero viciado o lento.
04 · Ejemplos
Tres sorteos típicos
- 1Rifa entre 200.
choice(participantes)con semilla registrada y auditoría pública. Si hay premio grande:secrets.choice. - 2Carta de mazo.
Con reemplazo (reponer): puede salir dos veces el as. Sin reposición es otro problema (Temas 24–25).
- 3Canción / anuncio rotativo.
Millones de sorteos O(1) sin mutar la lista:
choices(lista, k=miles)de una vez.
05 · Implementación en Python
Sortear y contar
Sorteamos 6000 veces entre 6 nombres con choice y con floor manual: ambos dan ≈ 1/6 por nombre. Contar es auditar.
Python en tu navegador. Cambiá la lista a 7 elementos y el % del laboratorio se comporta distinto: el resto manda.
import math
import random
from collections import Counter
nombres = ["Ana", "Bruno", "Carla", "Diego", "Elena", "Franco"]
rng = random.Random(21)
s1 = [rng.choice(nombres) for _ in range(6000)]
rng = random.Random(21)
s2 = [nombres[math.floor(rng.random() * len(nombres))] for _ in range(6000)]
for nombre, s in [("choice", s1), ("floor", s2)]:
c = Counter(s)
print(nombre, [round(c[n] / len(s), 4) for n in nombres])
Seis frecuencias ≈ 0,1667 en ambas vías: el índice floor es el motor de choice.
Sorteo con premio
import secrets
participantes = ["Ana", "Bruno", "Carla", "Diego", "Elena", "Franco"]
print(secrets.choice(participantes))
print(secrets.choice(participantes))
06 · Exploración
Laboratorio: ¿salen todos parejo?
Lista de n elementos (A, B, C…) sorteados N veces desde fuente 0 … 99. Compará floor (parejo) con % crudo: cuando 100 % n ≠ 0, los primeros elementos se disparan en naranja.
Sorteo entre n
choice vs % crudo
Con n = 6 y % crudo, 4 elementos salen de más.
La línea punteada es 1/n. Con floor todas las barras la rondan; con % y resto, un grupo queda sistemáticamente arriba.
Preguntas para explorar
- Con n = 5 (100 % 5 = 0) y
%: ¿hay sesgo? ¿Y con n = 6? - Con n = 6, floor y 12000 sorteos: ¿el desvío se achica? ¿Pasa lo mismo con
%? - Si sorteás de a un ganador por día durante un año, ¿cuánto premio de más se llevan los favorecidos por el
%?
Ver respuestas sugeridas
- Con n = 5 no hay resto: parejo. Con n = 6 el resto 4 vicia los 4 primeros (17/100 frente a 16/100).
- Floor sí se achica (ruido que se diluye);
%no: el sesgo es estructural y persiste con N. - ≈ 6 % de sorteos de más por favorecido (17 vs 16): en 365 días son ~22 premios de ventaja. Auditable y denunciable.
07 · Comprensión
Confusiones frecuentes
«Para uno solo, mezclo y tomo el primero»
Funciona pero cuesta O(n) y muta la lista: para un sorteo es 1000× más trabajo que choice O(1) sin efectos laterales.
«Con reemplazo y sin reemplazo dan lo mismo si saco uno»
Con uno solo coinciden; con varios difieren: con reemplazo hay repetidos, sin reemplazo no (Tema 25). Nombrarlo evita bugs de diseño.
«randint(0, len(lista)) elige bien»
Off-by-one: el índice válido llega a len−1. randint(0, len) incluye len → IndexError 1/(n+1) de las veces.
«Si salió 3 veces seguidas, está cargado»
Con reemplazo las repeticiones son normales (prob. 1/n² para un par): solo frecuencias sistemáticas en miles de sorteos denuncian sesgo.
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: rifa entre 200
Sorteá 3 ganadores con reemplazo entre 200 participantes y verificá que pertenecen a la lista.
import random
parts = [f"P{i:03d}" for i in range(200)]
rng = random.Random(4)
gan = [rng.choice(parts) for _ in range(3)]
print(gan, all(g in parts for g in gan))
Ver solución razonada
Tres códigos válidos con True. Con reemplazo podrían repetirse: si el reglamento lo prohíbe, es muestreo sin reemplazo (Tema 25), no triple choice.
Ejercicio 2: cazar el off-by-one
Provocá el IndexError de randint(0, len) y corregilo con randrange.
import random
xs = ["a", "b", "c", "d", "e", "f"]
rng = random.Random(1)
mal = 0
for _ in range(6000):
try:
xs[rng.randint(0, len(xs))]
except IndexError:
mal += 1
print("errores:", mal)
print(xs[rng.randrange(len(xs))])
Ver solución
≈ 857 errores (≈ 1/7): el índice len es inválido. randrange(len) o choice lo eliminan de raíz.
Ejercicio 3: auditor de sorteo
Escribí auditar(sorteos, elementos) que devuelva la frecuencia mínima y máxima, y probalo con 6000 sorteos.
Ver una posible respuesta
import random
from collections import Counter
def auditar(sorteos, elementos):
c = Counter(sorteos)
fs = [c[e] / len(sorteos) for e in elementos]
return min(fs), max(fs)
els = ["A", "B", "C", "D", "E", "F"]
rng = random.Random(8)
s = [rng.choice(els) for _ in range(6000)]
print([round(v, 4) for v in auditar(s, els)])
Mín y máx ≈ 0,1667 ± 0,015. Brechas mayores y estables entre elementos indican % viciado o lista mutada.
09 · Síntesis
Ideas para recordar
- Un elemento parejo:
lista[floor(u·n)]=choice, prob. 1/n, con reemplazo. %sobre fuente con resto favorece a los primeros; floor/rechazo no.choicees O(1) sin mutar; mezclar para uno es O(n) con efectos laterales.- Premio o ventaja →
secrets.choice; simulación →rng.choicecon semilla. - Índice válido
0 … n−1:randint(0, len)es off-by-one.
En el próximo tema mezclaremos todo: permutaciones aleatorias con Fisher-Yates.