¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Problema de asignación y transporte

Después de esta lección: Puedo resolver un problema de asignación pequeño.

Lección de 60 min3 comandosLaboratorio JupyterLab de ciencia de datos: pandas, Matplotlib y scikit-learn en el navegador

Repartir tres rutas entre tres repartidores

El problema de asignación reparte personas entre tareas, una para cada una, de modo que el coste total sea el menor. Sirve para repartir rutas, turnos o aulas.

La panadería tiene tres repartidores y tres rutas. Cada repartidor tarda un tiempo distinto en cada ruta, y hay que decidir quién va a cuál.

Al final de esta lección

Nota

Puedo resolver un problema de asignación pequeño.

Palabras clave

PalabraQué significaEjemplo
Problema de asignacióndar a cada persona una tarea distinta al menor coste total
Matriz de costesuna tabla con lo que cuesta cada persona en cada tarea
Permutaciónuna forma de ordenar los mismos elementos[2, 0, 1]
Fuerza brutaprobar todas las opciones y quedarse con la mejor
Problema de transportellevar mercancía de varios orígenes a varios destinos al menor coste

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

La matriz de costes guarda todos los tiempos

Cada fila es un repartidor y cada columna es una ruta. La celda dice cuántos minutos tarda ese repartidor en esa ruta.

Una asignación da a cada repartidor una ruta distinta. Su coste es la suma de las tres celdas elegidas.

Ejemplo resuelto: minutos por repartidor y ruta

RepartidorCentro (0)Puerto (1)Norte (2)
Ana947
Bruno685
Carla938

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Con tres repartidores hay seis repartos posibles

El primer repartidor puede elegir entre 3 rutas, el segundo entre las 2 que quedan y el tercero se lleva la última. Son 3 2 1 = 6 repartos.

Cada reparto es una permutación de las rutas. El orden [2, 0, 1] significa: Ana va a la ruta 2, Bruno a la 0 y Carla a la 1.

Ejemplo resuelto: los seis repartos y su coste

OrdenAna, Bruno, CarlaMinutos
[0, 1, 2]Centro, Puerto, Norte25
[0, 2, 1]Centro, Norte, Puerto17
[1, 0, 2]Puerto, Centro, Norte18
[1, 2, 0]Puerto, Norte, Centro18
[2, 0, 1]Norte, Centro, Puerto16
[2, 1, 0]Norte, Puerto, Centro24

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Calcula el coste de los seis repartos

Comando
docker exec jupyter python3 -c "import itertools; c = [[9, 4, 7], [6, 8, 5], [9, 3, 8]]; print(sorted((sum(c[i][p[i]] for i in range(3)), p) for p in itertools.permutations(range(3))))"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Prueba todos los repartos con dos bucles

Código: python
costs = [[9, 4, 7], [6, 8, 5], [9, 3, 8]]
orders = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
best_cost = 999
best_order = []
for order in orders:
    total = 0
    for courier in range(3):
        total = total + costs[courier][order[courier]]
    if total < best_cost:
        best_cost = total
        best_order = order
print("Reparto más barato:", best_order, "con", best_cost, "minutos")
# Cambia el 3 de Carla por un 10 y vuelve a ejecutar: ¿cambia el reparto?

Probar todos los repartos deja de ser posible

Con n repartidores hay n (n - 1) ... * 1 repartos, un número que se llama n factorial. Crece muy deprisa.

Con 5 repartidores son 120, con 10 son más de tres millones y con 15 son más de un billón. Un ordenador que pruebe un millón por segundo tardaría más de dos semanas.

Repartos posibles con 5, 10 y 15 personas

Comando
docker exec jupyter python3 -c "import math; print(math.factorial(5), math.factorial(10), math.factorial(15))"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Un error muy común

Advertencia

Mucha gente cree que basta con dar a cada persona su ruta más barata. Pero dos personas pueden querer la misma. Ana toma el Puerto (4) y Bruno el Norte (5). A Carla le queda el Centro (9). Son 18 minutos, peor que los 16 del mejor reparto.

Hay métodos que no prueban todos los repartos

El método húngaro resuelve la asignación sin probar todas las opciones, y SciPy lo trae en linear_sum_assignment. Con 15 repartidores contesta al instante.

El problema de transporte es el hermano mayor: cada origen tiene varias unidades y cada destino pide varias. La asignación es un transporte con una unidad en cada sitio. Los dos se pueden escribir como programas lineales.

Resuelve la asignación con SciPy

Comando
docker exec jupyter python3 -c "from scipy.optimize import linear_sum_assignment; c = [[9, 4, 7], [6, 8, 5], [9, 3, 8]]; rows, cols = linear_sum_assignment(c); print(list(cols), c[0][cols[0]] + c[1][cols[1]] + c[2][cols[2]])"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Para ir más lejos: cuatro repartidores

Añade un cuarto repartidor y una cuarta ruta a la matriz. ¿Cuántos repartos hay ahora? Prueba con linear_sum_assignment y comprueba que el mejor reparto sigue siendo el que esperabas.

Sobre este laboratorio y su código: Laboratorio JupyterLab de ciencia de datos: pandas, Matplotlib y scikit-learn en el navegador

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.