01 · Punto de partida
Sortear uno es fácil; mezclar todos, no
El Tema 23 sortea un ganador con choice. Pero repartir un mazo, ordenar un examen o rotar una playlist exigen todos los elementos en orden aleatorio: una permutación donde cada uno de los n! órdenes tenga prob. 1/n!. El atajo “recorro y cambio cada carta con otra al azar” parece funcionar y vicia el mazo: algunas órdenes salen el doble que otras.
La solución es de 1938 (Fisher-Yates, 1964 Knuth): ir fijando desde el final con un sorteo que se achica j ∈ 0…i. Tres líneas, O(n), parejo demostrable.
- ¿Por qué
jva de0…iy no de0…n−1? - ¿Cuántos órdenes parejos hay con n = 4?
- ¿Qué tiene de malo ordenar por clave aleatoria?
- ¿Por qué
shuffleno devuelve nada?
02 · Definición
n! órdenes, una sola receta pareja
Uniforme 1/n!
Con n = 3 hay 6 órdenes; con n = 52 hay 8e67. Parejo = cada uno con igual chance, verificable contando (sección 05).
Rango que se achica
En el paso i hay i+1 candidatos para el puesto i: j = floor(u·(i+1)). Achicar es lo que reparte 1/n! exacto.
In-place O(n)
Sin lista auxiliar: n−1 sorteos y n−1 swaps. Mezclar para sacar uno solo es despilfarro (Tema 23).
| Método | Caminos | Veredicto |
|---|---|---|
Fisher-Yates (j ≤ i) | n! exactos | Parejo 1/n! |
Naive (j < n siempre) | nⁿ (no múltiplo de n!) | Sesgado: unas salen de más |
| Ordenar por clave u | Parejo si claves continuas | O(n log n) + empates si clave discreta |
03 · Técnica correcta
El rango manda
Con n = 3 el naive hace 3³ = 27 caminos sobre 6 órdenes: 27 no es múltiplo de 6, así que es imposible repartir parejo (unas salen 5/27, otras 4/27). Fisher-Yates hace 3·2·1 = 6 caminos: biyección perfecta con los 6 órdenes.
- j ≤ i siempre
- El error clásico es
randint(0, n−1)en cada paso. Lo correcto:randint(0, i)con i decreciente. Un carácter de diferencia, sesgo total. - Entero sin % viciado
- El
jdebe ser parejo en0…i: con fuente entera usar rechazo si hay resto (Tema 22) o floor sobre u (Tema 19). - shuffle muta
random.shuffle(mazo)mezcla in-place y devuelveNone:m = shuffle(m)dejam = None. Para copiar:sample(mazo, len(mazo)).
Bien mezclado
Fisher-Yates / shuffle
O(n), 1/n! demostrable, con semilla para auditar. El estándar de casinos simulados y exámenes.
Mal mezclado
Naive o sort por rand()
Sesgo 5/27 vs 4/27 con n = 3 (medible), o costo O(n log n) con riesgo de empates. Para un mazo real, trampa.
04 · Ejemplos
Tres mezclas típicas
- 1Mazo de 52.
shuffle(mazo)con semilla registrada: 51 sorteos, 8e67 órdenes parejos. - 2Examen de 10 preguntas.
Cada alumno recibe una permutación distinta: sin ventaja de posición.
- 3Playlist sin “siempre primero lo mismo”.
Mezcla completa O(n) una vez, no
choicerepetido (que repite temas, Tema 23).
05 · Implementación en Python
Fisher-Yates a mano y conteo 1/n!
Implementamos FY con randrange(i+1) y verificamos con n = 3: las 6 permutaciones rondan 1/6 en 6000 mezclas. El naive no lo logra.
Python en tu navegador. Cambiá a randrange(n) (naive) y mirá cómo dos órdenes se disparan.
import random
from collections import Counter
def fisher_yates(xs, rng):
a = list(xs)
for i in range(len(a) - 1, 0, -1):
j = rng.randrange(i + 1)
a[i], a[j] = a[j], a[i]
return a
rng = random.Random(24)
c = Counter(tuple(fisher_yates("ABC", rng)) for _ in range(6000))
for perm, n in sorted(c.items()):
print("".join(perm), round(n / 6000, 4))
Seis valores ≈ 0,1667: biyección 6 caminos → 6 órdenes.
El naive se delata
import random
from collections import Counter
def naive(xs, rng):
a = list(xs)
n = len(a)
for i in range(n):
j = rng.randrange(n)
a[i], a[j] = a[j], a[i]
return a
rng = random.Random(24)
c = Counter(tuple(naive("ABC", rng)) for _ in range(6000))
for perm, n in sorted(c.items()):
print("".join(perm), round(n / 6000, 4))
06 · Exploración
Laboratorio: 6 órdenes bajo lupa
Con n = 3 hay 6 permutaciones: las barras deben rondar 1/6. Compará Fisher-Yates con el naive: el naive deja dos barras altas sistemáticas (5/27 frente a 4/27). Subí los ensayos: el sesgo no se diluye.
1/6 o nada
FY vs naive · n = 3
FY con 6000 ensayos: seis barras en 1/6.
La línea es 1/6 ≈ 0,1667. Barras naranjas sistemáticamente altas = órdenes favorecidos por el algoritmo.
Preguntas para explorar
- Con naive y 12000 ensayos: ¿el χ² se achica o crece? ¿Qué dice de sesgo vs ruido?
- ¿Qué dos órdenes favorece el naive? ¿Coinciden con la teoría 5/27?
- Si mezclaras un mazo de 52 con naive, ¿detectarías el sesgo contando? ¿Por qué no?
Ver respuestas sugeridas
- Crece: el sesgo es estructural (≈ 0,0185 de ventaja) y el χ² escala con N. El ruido se diluye; el sesgo, no.
- Dos órdenes en ≈ 0,1852 (5/27) y cuatro en ≈ 0,1481 (4/27): la firma 27 ∤ 6.
- No contando (8e67 órdenes): por teoría (nⁿ caminos) y por usar FY/shuffle auditados, no por censo.
07 · Comprensión
Confusiones frecuentes
«Ordeno por random() y listo»
Con claves continuas es parejo pero O(n log n) y frágil: con claves discretas (randint) los empates rompen el 1/n!. FY es O(n) y exacto.
«m = shuffle(lista) me da la mezclada»
shuffle devuelve None y muta: m queda en None y la original mezclada. Para nueva lista: sample.
«j en 0…n−1 también mezcla»
Mezcla pero vicia: 27 caminos sobre 6 órdenes con n = 3. El rango que se achica no es decoración, es la prueba.
«Para sorteo uso shuffle y tomo el primero»
O(n) con mutación para un O(1) sin mutación (choice, Tema 23). Funciona, pero es 1000× más trabajo con efectos laterales.
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: mazo de 52
Mezclá un mazo con shuffle y verificá que están las 52 cartas sin repetidos ni faltantes.
import random
mazo = [f"{v}{p}" for v in "A23456789JQK" for p in "♠♥♦♣"]
rng = random.Random(10)
rng.shuffle(mazo)
print(mazo[:5], len(mazo), len(set(mazo)))
Ver solución razonada
5 cartas visibles, 52 totales y 52 distintas: permutación completa. Con choices habría repetidos (Tema 25).
Ejercicio 2: medir el naive
Repetí el conteo del naive con 30000 ensayos y comprobá el 5/27 ≈ 0,1852 en los favorecidos.
import random
from collections import Counter
def naive(xs, rng):
a = list(xs)
for i in range(len(a)):
j = rng.randrange(len(a))
a[i], a[j] = a[j], a[i]
return a
rng = random.Random(24)
c = Counter(tuple(naive("ABC", rng)) for _ in range(30000))
print(sorted((round(n / 30000, 4) for n in c.values())))
Ver solución
Cuatro valores ≈ 0,148 y dos ≈ 0,185: la firma 4/27 y 5/27. Con 30000 el ruido (±0,004) ya no tapa el sesgo (±0,019).
Ejercicio 3: copia sin mutar
Devolvé una mezcla nueva sin tocar la original de dos formas: FY manual y sample.
Ver una posible respuesta
import random
orig = list("ABCDE")
rng = random.Random(2)
fy = list(orig)
for i in range(len(fy) - 1, 0, -1):
j = rng.randrange(i + 1)
fy[i], fy[j] = fy[j], fy[i]
otra = rng.sample(orig, len(orig))
print(orig, fy, otra)
Original intacta y dos mezclas parejas distintas. shuffle directo habría mutado orig.
09 · Síntesis
Ideas para recordar
- Fisher-Yates: j ∈ 0…i de atrás adelante, 1/n!, O(n), in-place.
- Naive con j < n: nⁿ caminos, sesgo 5/27 vs 4/27 con n = 3.
shufflemuta y devuelve None;samplecopia.- El j debe ser parejo: rechazo/floor, nunca % con resto (Tema 22).
- Sattolo (j < i) es otro algoritmo: un ciclo, no mezcla general.
En el próximo tema tomaremos porciones: muestreo aleatorio con y sin reemplazo.