Deux fichiers situé dans le même repertoire que cette feuille:
Les fichiers doivent être au format CSV.
parrains.csv
filleuls.csv
Le programme calcule une affectation et l'écrit dans le fichier parrainage.csv
.
Nom des fichiers Les noms des fichiers sont définis par les variables suivantes.
In [ ]:
# Nom de fichiers
fichierParrain = "parrains.csv"
fichierFilleul = "filleuls.csv"
fichierResultat = "parrainage.csv"
In [ ]:
# Imports
import csv
import glob
import pulp # LP
# pulp.pulpTestAll() # Test
In [ ]:
def fetch_row_parrain(row, line, file):
"""
Renvoie une ligne de type `parrain`
"""
try:
return row[0], [row[1], row[2], row[3]]
except:
print("Erreur de lecture fichier {}, ligne: {}".format(file, line))
return []
def fetch_row_filleul(row, line, file):
"""
Renvoie une ligne de type `filleul`
"""
try:
return row[0], [row[1], row[2], row[3]]
except:
print("Erreur de lecture fichier {}, ligne: {}".format(file, line))
return []
In [ ]:
def fetch_row(row, line, file, category):
"""
Renvoie une ligne sous forme d'un tableau
Input:
- row: ligne
- line: numéro de la ligne dans le fichier
- file: nom du fichier
- category: `parrain` ou `filleul`
Output:
- un tableau
"""
if category == "parrain":
return fetch_row_parrain(row, line, file)
elif category == "filleul":
return fetch_row_filleul(row, line, file)
else:
raise Exception("Un fichier doit être de type `parrain` ou `filleul`")
def fetch_from_file(file, category):
"""
Importe un fichier cvs dans un dictionnaire
Input:
- file: nom du fichier
- category: `parrain` ou `filleul`
"""
data = dict()
with open (file, 'r') as csv_file:
spamreader = csv.reader(csv_file, delimiter=';', skipinitialspace=True)
for row in spamreader:
# Fetch line
line = fetch_row(row, spamreader.line_num, file, category)
if line == []: # Si erreur de lecture, passer au suivant
continue
if line[0] in data:
print("Doublon {}, dans le fichier {}".format(line[0], file))
else:
data[line[0]] = line[1]
return data
In [ ]:
dataParrains = fetch_from_file(fichierParrain, "parrain")
dataFilleuls = fetch_from_file(fichierFilleul, "filleul")
# On s'assure que les tables construites ne sont pas vides...
assert(len(dataParrains) > 0)
assert(len(dataFilleuls) > 0)
Recherche d'un matching optimal entre une liste de parrains et une liste de filleuls.
Les pénalités sont les suivantes (voir la fonction matching
)
In [ ]:
def matching(s, t):
"""
Renvoie une note de compatibilité entre `s` et `t`.
Plus la note est élevée, moins `s` et `t` sont compatibles.
"""
res = 1
if (s[0] != t[0]): # Option
res += 7
if (s[1] != t[1]): # Primant
res += 2
if (s[2] == t[2]): # Tutorat
res += 10
return res
In [ ]:
arrow_set = [(s, t) for s in dataParrains for t in dataFilleuls]
assert(len(arrow_set) == len(dataParrains) * len(dataFilleuls))
# Variables
print("Variables...")
arrows = pulp.LpVariable.dicts('affectation', arrow_set, lowBound = 0, upBound = 1, cat = pulp.LpInteger)
# Modèle
print("Modèle...")
model = pulp.LpProblem("Parrainage", pulp.LpMinimize)
# Objectif
print("Objectif...")
model.setObjective(pulp.lpSum([matching(dataParrains[s], dataFilleuls[t]) * arrows[(s, t)]
for (s, t) in arrow_set]))
In [ ]:
# Au plus 4 filleuls par parrain
for s in dataParrains:
model.addConstraint(pulp.lpSum(arrows[(s, t)] for t in dataFilleuls) <= 4)
# Exactement 1 parrain
for t in dataFilleuls:
model.addConstraint(pulp.lpSum(arrows[(s, t)] for s in dataParrains) == 1)
In [ ]:
print(model.solve())
parrainage = [a for a in arrow_set if arrows[a].value() == 1]
In [ ]:
# Écrit la solution dans `fichierResultat`
with open(fichierResultat, 'w') as csv_file:
writer = csv.writer(csv_file)
writer.writerows(parrainage)
# Afficher la solution ici
# print("Parrain -> Filleul\n")
# for x in parrainage:
# print("{} -> {}".format(x[0], x[1]))
In [ ]: