Bowser parece la elección obvia en Mario Kart 8: tiene más velocidad que casi cualquier otro personaje. Pero un piloto rápido que arranca lento después de un golpe pierde más carreras de las que gana, y ahí es exactamente donde falla la intuición al elegir entre miles de combinaciones posibles.
El desarrollador Antoine Mayerowitz explicó en un artículo reciente cómo aplicar la frontera de Pareto, un concepto de comienzos del siglo XX, para resolver este tipo de decisiones con múltiples variables en conflicto. La misma lógica sirve para elegir un modelo de IA, una arquitectura de base de datos o cualquier trade-off técnico.
TL;DR
- La frontera de Pareto identifica las opciones que ningún otro punto domina en dos o más métricas a la vez.- En Mario Kart 8 cada piloto, kart, rueda y planeador aporta estadísticas propias de velocidad, aceleración, manejo, peso, offroad y mini turbo.- Un build está dominado cuando existe otro con igual o mejor valor en todas las métricas y mejor en al menos una.- El concepto lo formalizó el economista italiano Vilfredo Pareto a comienzos del siglo XX estudiando asignación de recursos.- Calcular la frontera con dos métricas cuesta O(n log n) ordenando por un eje, frente a O(n²) de comparar todo contra todo.- El mismo algoritmo sirve para elegir modelos de lenguaje por costo y latencia, o instancias cloud por precio y memoria.- Librerías como DEAP implementan NSGA-II para cuando hay más de tres o cuatro métricas en juego.
Introducción
Elegir personaje en Mario Kart 8 no es solo estética. Cada piloto, carrocería, juego de llantas y planeador aporta valores propios de velocidad, aceleración, manejo, peso, tracción en tierra (offroad) y mini turbo. Multiplicá las decenas de opciones en cada categoría y el número de combinaciones posibles se cuenta en miles.
La mayoría de esas combinaciones son ruido: dos builds distintos pueden compartir exactamente las mismas estadísticas y diferir solo en el diseño visual. Pero incluso después de eliminar esos duplicados, queda un problema real de decisión con múltiples variables que compiten entre sí. Ir más rápido casi siempre cuesta manejo o aceleración. No hay una sola respuesta correcta, hay una familia de respuestas razonables, y encontrarla a mano es imposible.
Qué pasó
El artículo "Mario meets Pareto", publicado por el desarrollador Antoine Mayerowitz en su blog personal, usa el juego como excusa para explicar un concepto que cualquier persona que programa debería tener en su caja de herramientas: la frontera de Pareto.
El ejemplo que usa es directo. Comparando solo velocidad y aceleración, Koopa Troopa queda descartado sin ambigüedad: Cat Peach tiene más velocidad con la misma aceleración, y Toadette tiene más aceleración con la misma velocidad. No existe ningún escenario donde elegir a Koopa sea mejor que elegir a cualquiera de las otras dos. En el lenguaje de la optimización, Koopa está dominado.
Ese razonamiento, repetido personaje por personaje y kart por kart, permite descartar automáticamente todas las opciones objetivamente peores sin necesidad de decidir de antemano cuánto pesa la velocidad frente a la aceleración.
Contexto e historia: qué es la frontera de Pareto
El concepto lleva el nombre del economista italiano Vilfredo Pareto, que a comienzos del siglo XX estudió cómo asignar recursos entre múltiples partes sin que mejorar la situación de una signifique necesariamente empeorar la de otra. Una asignación es Pareto-eficiente cuando no existe ninguna alternativa que mejore a alguien sin perjudicar a alguien más.
Fuera de la economía, la misma idea describe cualquier situación donde hay que optimizar dos o más objetivos en conflicto al mismo tiempo: un plato que sea barato y sabroso, un trabajo bien pagado y con horario flexible, un portafolio de bajo riesgo y alto retorno. En ingeniería de software el patrón es constante: un modelo de lenguaje rápido y barato, una base de datos con lecturas veloces y escrituras consistentes, una imagen de contenedor liviana y con pocas vulnerabilidades.
Cuando conocés de antemano el peso exacto que le das a cada variable, el problema se reduce a una sola métrica combinada (una función de utilidad) y no hace falta Pareto: comparás el número final y listo. El caso interesante, y el que resuelve la frontera de Pareto, es cuando esos pesos no están definidos o cambian según el contexto.
Detalles técnicos y rendimiento
Formalmente, un punto A domina a un punto B si A es igual o mejor que B en todas las métricas y estrictamente mejor en al menos una. La frontera de Pareto es el conjunto de puntos que ningún otro punto domina.
Calcularla por fuerza bruta comparando cada opción contra todas las demás cuesta O(n²), que para un catálogo de miles de builds de Mario Kart 8, o miles de instancias cloud, o cientos de checkpoints de un modelo, empieza a doler. Para dos dimensiones existe un atajo: ordenar por una métrica y recorrer la lista una sola vez, quedándote con cada punto que mejora el máximo visto hasta ahora en la segunda métrica. Eso baja el costo a O(n log n), dominado por el ordenamiento inicial.
def pareto_front(options, key_x, key_y):
ordenadas = sorted(options, key=lambda o: o[key_x], reverse=True)
frontera = []
mejor_y = float("-inf")
for opcion in ordenadas:
if opcion[key_y] > mejor_y:
frontera.append(opcion)
mejor_y = opcion[key_y]
return frontera
builds = [
{"nombre": "Koopa Troopa", "velocidad": 3, "aceleracion": 4},
{"nombre": "Cat Peach", "velocidad": 4, "aceleracion": 4},
{"nombre": "Toadette", "velocidad": 3, "aceleracion": 5},
{"nombre": "Bowser", "velocidad": 6, "aceleracion": 2},
]
for build in pareto_front(builds, "velocidad", "aceleracion"):
print(build["nombre"])
Con este dataset de ejemplo, la función descarta a Koopa Troopa (dominado por Cat Peach y Toadette) y devuelve a Bowser, Cat Peach y Toadette como la frontera eficiente. Ninguno de los tres es objetivamente mejor que otro: dependen de si preferís velocidad o recuperación tras un golpe.
Con tres métricas o más (velocidad, aceleración, manejo, peso, offroad y mini turbo, como tiene Mario Kart 8), el atajo de ordenar y recorrer deja de alcanzar y hace falta comparar cada par de puntos, o recurrir a heurísticas como las de DEAP, pensadas para explorar espacios de búsqueda grandes sin fuerza bruta completa.
Los puntos dominados quedan por debajo y a la izquierda de la frontera.EnfoqueCuándo usarloVentajaLimitaciónFunción de utilidad (suma ponderada)Cuando ya sabés cuánto pesa cada métricaDevuelve una única mejor opciónExige definir los pesos de antemanoFrontera de Pareto (ordenar y recorrer)Dos métricas, pesos desconocidosO(n log n), sin comprometerse a un pesoNo elige un ganador, solo filtraFuerza bruta (comparar todos contra todos)Pocas opciones o más de dos métricasSimple de implementar y verificarO(n²), no escala a catálogos grandesNSGA-II y heurísticas evolutivasMuchas métricas y espacios de búsqueda enormesEscala a alta dimensionalidadAproximado, más complejo de afinar
flowchart TD
A["Ordenar opciones por metrica X descendente"] --> B["Tomar la siguiente opcion de la lista"]
B --> C{"Su metrica Y supera el maximo visto?"}
C -->|"si"| D["Agregarla a la frontera y actualizar el maximo"]
C -->|"no"| E["Descartarla, esta dominada"]
D --> F{"Quedan opciones?"}
E --> F
F -->|"si"| B
F -->|"no"| G["Frontera de Pareto lista"]
💡 Tip: si tu problema tiene más de tres métricas, filtrá primero por Pareto para reducir el catálogo y recién ahí aplicá pesos o criterio manual sobre el subconjunto que quedó.
Cómo probarlo
No hace falta instalar nada para reproducir el ejemplo: la función pareto_front de arriba es Python puro, sin dependencias. Guardala en un archivo pareto.py y correla con el intérprete que ya tengas instalado.
# Windows (PowerShell)
python pareto.py
# macOS / Linux
python3 pareto.py
Para un caso más cercano al día a día de un equipo de software, el mismo patrón sirve para filtrar opciones de infraestructura antes de decidir. Este segundo ejemplo compara instancias hipotéticas por costo mensual (a minimizar) y memoria disponible (a maximizar); invertí el signo del costo para que la función siga buscando "mayor es mejor" en ambos ejes:
instancias = [
{"nombre": "t3.medium", "costo": 30, "memoria_gb": 4},
{"nombre": "t3.large", "costo": 60, "memoria_gb": 8},
{"nombre": "m5.large", "costo": 70, "memoria_gb": 8},
{"nombre": "t3.xlarge", "costo": 120, "memoria_gb": 16},
]
for i in instancias:
i["costo_invertido"] = -i["costo"]
frontera = pareto_front(instancias, "memoria_gb", "costo_invertido")
print([i["nombre"] for i in frontera])
m5.large queda afuera de la frontera en este dataset de ejemplo porque t3.large ofrece la misma memoria a menor costo: está dominada. Para confirmar que el filtro no descartó nada por error, corré en paralelo la versión de fuerza bruta O(n²) (comparar cada instancia contra todas las demás con dos bucles anidados) sobre el mismo dataset y verificá con un assert que ambos conjuntos resultantes son idénticos.
Impacto y análisis
Filtrar por Pareto reduce el catálogo antes de decidir con criterio.
La frontera de Pareto no elige por vos. Elimina lo objetivamente peor y te deja con un conjunto de opciones donde mejorar una métrica implica necesariamente resignar otra. Elegir dentro de ese conjunto sigue siendo una decisión humana, informada por el contexto: un equipo con presupuesto ajustado prioriza costo, uno con SLA estricto prioriza latencia.
La ventaja práctica frente a fijar pesos de antemano es que no tenés que comprometerte con una fórmula antes de ver los datos. Podés calcular la frontera, mirar las tres o cuatro opciones que sobrevivieron, y recién ahí discutir con el equipo cuál conviene según la prioridad del momento. Es más fácil defender "elegimos entre estas tres alternativas eficientes" que "elegimos la que dio el número más alto en una fórmula que inventamos ayer".
Hay un límite real que conviene conocer antes de aplicar esto a ciegas: a medida que crecen las dimensiones (más de tres o cuatro métricas simultáneas), la fracción de puntos que queda en la frontera tiende a crecer también. Es la maldición de la dimensionalidad aplicada a la optimización multiobjetivo: con demasiadas métricas, casi ninguna opción domina a otra en todo, y el filtro pierde poder de descarte. En esos casos conviene agrupar métricas relacionadas en un puntaje compuesto antes de calcular la frontera, o aceptar que hace falta criterio humano para las últimas dos o tres opciones.
⚠️ Ojo: con más de tres o cuatro métricas a la vez, la frontera de Pareto puede terminar incluyendo casi todo el catálogo y perder utilidad como filtro.
Qué sigue
La comparación de opciones por más de una métrica a la vez ya es habitual en tableros que evalúan modelos de lenguaje por costo y calidad en simultáneo, en vez de un único ranking. El mismo criterio se puede aplicar a cualquier catálogo interno de un equipo: versiones de una librería, configuraciones de build, tipos de índice en una base de datos. La frontera de Pareto no requiere infraestructura nueva ni una herramienta específica: es una función de menos de diez líneas que cualquier equipo puede correr sobre su propia tabla de comparación antes de la próxima decisión técnica.
📖 Resumen en Telegram: Ver resumen
Probalo vos: copiá la función pareto_front de este artículo, cargá el costo y la latencia de las opciones que estés evaluando esta semana y mirá cuántas sobreviven al filtro.
Preguntas frecuentes
¿Qué significa que una opción sea Pareto-eficiente?
Que ninguna otra opción del conjunto es igual o mejor en todas las métricas y estrictamente mejor en al menos una. Si existe una opción así, la primera queda dominada y se descarta.
¿La frontera de Pareto me dice cuál es la mejor opción?
No. Reduce el conjunto a las opciones que valen la pena considerar, pero elegir una de ellas depende de qué tanto pesa cada métrica para tu caso, algo que solo vos o tu equipo pueden definir.
¿Cómo se calcula con más de dos métricas?
El atajo de ordenar y recorrer una sola vez solo funciona con dos dimensiones. Con tres o más hay que comparar cada opción contra todas las demás, o usar heurísticas como NSGA-II cuando el catálogo es muy grande.
¿Sirve para decisiones que no son técnicas?
Sí. El artículo original de Antoine Mayerowitz lo aplica también a elegir comida, trabajo o portafolio de inversión: cualquier decisión con dos o más objetivos en conflicto es candidata.
¿Qué diferencia hay con una función de utilidad?
Una función de utilidad combina las métricas en un solo número usando pesos definidos de antemano y te da directamente un ganador. La frontera de Pareto no exige esos pesos: solo elimina lo objetivamente peor y te deja el resto para decidir.
¿Qué pasa si dos opciones tienen exactamente los mismos valores?
Ninguna domina a la otra porque ninguna es estrictamente mejor en algo, así que ambas quedan dentro de la frontera. La decisión entre ellas pasa a depender de otro criterio, como preferencia personal o disponibilidad.
Referencias
- Mario meets Pareto: el artículo original de Antoine Mayerowitz que inspiró esta nota, con una visualización interactiva de los builds de Mario Kart 8.- Pareto efficiency, Wikipedia: definición formal y ejemplos del concepto de eficiencia de Pareto en economía.- Multi-objective optimization, Wikipedia: panorama de algoritmos y aplicaciones de la optimización con múltiples objetivos.- DEAP: framework de Python con implementaciones de NSGA-II y otros algoritmos evolutivos multiobjetivo.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Top comments (0)