Deux fichiers situé dans le même repertoire que cette feuille:
Les fichiers doivent être au format CSV.
parrains.csvfilleuls.csvLe 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 [ ]: