# coding: utf-8
# # Table of Contents
#
# # Implémentation simplifiée des algorithmes de ParcourSup
#
# > - Pour plus de détails, voir [le projet sur GitHub](https://github.com/Naereen/ParcourSup.py/).
# > - Auteur(s) : [Lilian Besson](https://github.com/Naereen/) et [Bastien Trotobas](https://github.com/BastienTr).
# > - Date : Juillet 2018.
# > - Licence : [MIT](https://lbesson.mit-license.org/)
# On utilise Python 3 dans ce document. Je vous conseille de le lire interactivement, en cliquant sur ce bouton là :
#
# https://mybinder.org/v2/gh/Naereen/ParcourSup.py/master?filepath=notebooks%2FParcourSup.py_version_simplifiee.ipynb
# In[1]:
get_ipython().run_line_magic('load_ext', 'watermark')
get_ipython().run_line_magic('watermark', '-a "Lilian Besson" -v')
# ## Algorithme 1 : Ordre d'appel
#
# On va donner ici une implémentation simplifiée de la fonction du calcul d'ordre d'appel, avec plusieurs exemples.
# ### Types de candidats
#
# Un candidat ou une candidate peut être boursier-ère ou non, et résident-e ou non.
# On a besoin de 4 types de candidats, qu'on représente simplement par un entier.
# In[2]:
# Les différents types de candidats
BoursierResident = 1
BoursierNonResident = 2
NonBoursierResident = 3
NonBoursierNonResident = 4
# In[3]:
# Liste des différents types
TypesCandidats = [
BoursierResident,
BoursierNonResident,
NonBoursierResident,
NonBoursierNonResident
]
# On définie également une fonction pour pouvoir afficher le type d'un candidat.
# In[4]:
def afficheTypeCandidat(typeCandidat):
if typeCandidat == BoursierResident:
return "BoursierResident"
elif typeCandidat == BoursierNonResident:
return "BoursierNonResident"
elif typeCandidat == NonBoursierResident:
return "NonBoursierResident"
elif typeCandidat == NonBoursierNonResident:
return "NonBoursierNonResident"
# ### Un vœu
#
# Un vœu désigne la candidature d'un candidat à une formation donnée. Ici on représente un vœu comme la donnée du type de candidat et du rang que l'établissement lui a atribué.
# On pourrait utiliser un tuple ou une liste pour représenter un vœu, par exemple `[BoursierResident, 1, "Harry Potter"]` pour un boursier résident classé 1er et appelé Harry Potter, mais par lisibilité on préfère utiliser un dictionnaire qui contient ces informations :
# > *POUR NOUS* ne même pas parler de nom mais juste d'identifiant ?
# In[38]:
exemple_voeu = {
"type": BoursierResident,
"rang": 1,
"nom": "Harry Potter",
"id": 1235
}
# En fait, on n'utilisera jamais le nom des vœux, parce que *la plateforme ParcourSup ne tient compte d'aucune information sur les vœux ou les candidat-s, à part leur identifiant unique et anonymisé*.
#
# On ajoute un nom dans les premiers exemples simplement pour être un peu plus visuel.
# Les seules chosent que l'algorithme de ParcourSup utilise à propos des vœux sont leur type et leur rang.
# Dans le code ci-dessous, ils seront lu comme ça : `voeu["type"]` et `voeu["rang"]`.
# In[6]:
print("Vœu de type", afficheTypeCandidat(exemple_voeu["type"]), "et de rang", exemple_voeu["rang"])
# ### Une liste de vœux
# Ensuite, une liste de vœux est une liste de dictionnaires de cette forme.
# Prenons un exemple avec 8 vœux, qui reprend l'exemple "A1" donné dans le document de référence.
#
# > Note : les prénoms sont choisis pour être dans l'ordre alphabétique.
# In[39]:
voeu_alice = { "type": NonBoursierNonResident, "rang": 1, "nom": "Alice" , "id": 1911}
voeu_bob = { "type": NonBoursierNonResident, "rang": 2, "nom": "Bob", "id": 2211}
voeu_christophe = { "type": NonBoursierNonResident, "rang": 3, "nom": "Christophe", "id": 1061}
voeu_dora = { "type": NonBoursierNonResident, "rang": 4, "nom": "Dora", "id": 7843}
voeu_emilie = { "type": NonBoursierNonResident, "rang": 5, "nom": "Emilie", "id": 3388}
voeu_florian = { "type": BoursierNonResident, "rang": 6, "nom": "Florian", "id": 4073}
voeu_guillaume = { "type": BoursierNonResident, "rang": 7, "nom": "Guillaume", "id": 8020}
voeu_helene = { "type": NonBoursierNonResident, "rang": 8, "nom": "Hélène", "id": 4219}
# On a juste à les mettre dans une liste ensuite (en Python, une liste commence par `[`, chaque valeur est séparée par `,` et termine par `]`).
# In[8]:
voeuxClasses = [
voeu_alice, voeu_bob, voeu_christophe, voeu_dora,
voeu_emilie, voeu_florian, voeu_guillaume, voeu_helene
# le retour à la ligne n'est là que pour une meilleure visibilité
]
# On aura besoin de savoir si un type de candidat est boursier ou non (respectivement, est résident ou non). On ecrit deux fonctions pour faire ce test qui renvoie *True* si le candidat est du type proposé et *False* si ce n'est pas le cas.
# In[9]:
def estBoursier(voeu):
return voeu["type"] == BoursierResident or voeu["type"] == BoursierNonResident
# In[10]:
def estResident(voeu):
return voeu["type"] == BoursierResident or voeu["type"] == NonBoursierResident
# In[11]:
print("Le vœu exemple est-il boursier ?", estBoursier(exemple_voeu))
print("Le vœu exemple est-il résident ?", estResident(exemple_voeu))
# ### Contraintes
#
# L'algorithme ParcoursSup permet aux établissements de fixer des quotas minimaux sur le nombre de boursiers-ères et de résidents-es admis. On a besoin de connaître ces deux contraintes, exprimées en pourcentage donné comme un *entier* entre $0$ et $100$.
#
# - Par exemple ici, on demandera à avoir au moins 20% de boursiers-ères, mais aucune contrainte sur le taux de résidents-e-s.
# In[12]:
tauxMinBoursiersPourcents = 20
tauxMinResidentsPourcents = 0
# ### Algorithme
#
# On devrait avoir tout ce qu'il faut.
#
# L'algorithme est présenté comme suit dans [le document de référence](https://framagit.org/parcoursup/algorithmes-de-parcoursup/blob/master/doc/presentation_algorithmes_parcoursup.pdf) :
#
#
#
# Nous avons essayé de rendre le code suivant le plus clair possible mais il est nécessairement un peu long. Forcez vous à le lire jusqu'au bout !
#
# La fonction suivante passe par plusieurs étapes.
#
# > Je trouve le terme voeuxClasses ambigue, voeuxFormation pour l'ensemble des voeux d'une formation serait plus clair. Ton avis ?
#
# **Données d'entrée :**
# - voeuxClasses : la listes des voeux postulant à une formation.
# - tauxMinBoursiersPourcents : le pourcentage minimale de boursiers que l'établissement souhaite recruter.
# - tauxMinResidentsPourcents : le pourcentage minimale de résidents que l'établissement souhaite recruter.
# - afficheTout : variable qui choisie si le fonction affiche ou non les explications.
#
# **Grosses étapes :**
# 1. Tri de la liste voeuxClasses selon le rang affecté à chaque candidat-e.
# 1. On sépare la liste en quatres liste d'attente ordonnées, une par type de candidat-e.
# 1. On créé la liste ordreAppel qui servira appeler les candidats-es sur le site. Elle est construite en ajoutant un par un tous-tes les candidats-es qui ont demandé cette formation. Pour cela on répète les actions jusqu'à ce que tous-tes les candidats-rs soient traités.
# 1. On vérifie si les quotas fixés sont respectés et on détermine le type de candidat-e a recruter en fonction.
# 1. On place à la suite de la liste ordreAppel le-a meilleur-e candidat-e du type choisi et on le retire de sa liste d'attente.
# In[69]:
def calculerOrdreAppel(
voeuxClasses,
tauxMinBoursiersPourcents,
tauxMinResidentsPourcents,
afficheTout=True
):
# On définit la fonction d'affichage que si on demande d'afficher tout.
if afficheTout == False:
def affiche(*args): pass
else: affiche = print
# 1. On tri la liste voeuxClasses par ordre de rang
affiche("\n1. On trie les vœux par rang")
affiche(" Avant le tri :", voeuxClasses)
voeuxClasses.sort(key=lambda voeu: -voeu["rang"])
affiche(" Après le tri :", voeuxClasses)
# 2. On crée des listes de vœux pour chaque types de candidats
affiche("\n2. On crée des listes triées pour chaque types de candidats")
filesAttente = {
BoursierResident: [ ], # liste vide associée à chaque type
BoursierNonResident: [ ], # liste vide associée à chaque type
NonBoursierResident: [ ], # liste vide associée à chaque type
NonBoursierNonResident: [ ], # liste vide associée à chaque type
}
# Chaque voeu classé est placé dans la liste correspondante,
# en fonction du type du candidat.
# Les quatre listes obtenues sont ordonnées par rang de classement,
# comme l'est la liste voeuxClasses.
nbBoursiersTotal = 0
nbResidentsTotal = 0
for voeu in voeuxClasses:
# on ajoute le voeu à la fin de la file (FIFO) correspondante
filesAttente[voeu["type"]].append(voeu)
if estBoursier(voeu):
nbBoursiersTotal += 1
if estResident(voeu):
nbResidentsTotal += 1
# Affichage des listes d'attentes
affiche(" Liste des boursiers résident :\n\t", filesAttente[1])
affiche(" Liste des boursiers non résident :\n\t", filesAttente[2])
affiche(" Liste des boursiers non résident :\n\t", filesAttente[3])
affiche(" Liste des non boursiers non résident :\n\t", filesAttente[4])
nbVoeuxClasses = len(voeuxClasses)
nbAppeles = 0
nbBoursiersAppeles = 0
nbResidentsAppeles = 0
# la boucle ajoute les candidats un par un à la liste suivante, dans l'ordre d'appel.
# On commence par un ordre d'appel vide (liste vide).
ordreAppel = [ ]
affiche("\n3. Début de la boucle while, on remplit l'ordre d'appel pour respecter les critères")
while len(ordreAppel) < nbVoeuxClasses:
# on calcule lequel ou lesquels des critères boursiers et résidents
# contraignent le choix du prochain candidat dans l'ordre d'appel
contrainteTauxBoursier = (nbBoursiersAppeles < nbBoursiersTotal) and ((nbBoursiersAppeles * 100) < tauxMinBoursiersPourcents * (nbAppeles + 1))
affiche("\tLes boursiers représentent : ", (nbBoursiersAppeles * 100), "% et on en veut ", tauxMinBoursiersPourcents * (nbAppeles + 1), "%")
affiche("\tIl reste des boursiers à appeler : ", (nbBoursiersAppeles < nbBoursiersTotal))
affiche("\t\t=> Du coup on va appeler un boursier : ", contrainteTauxBoursier)
contrainteTauxResident = (nbResidentsAppeles < nbResidentsTotal) and ((nbResidentsAppeles * 100) < tauxMinResidentsPourcents * (nbAppeles + 1))
affiche("\tLes résident représentent : ", (nbResidentsAppeles * 100), "% et on en veut ", tauxMinResidentsPourcents * (nbAppeles + 1), "%")
affiche("\tIl reste des résidents à appeler : ", (nbResidentsAppeles < nbResidentsTotal))
affiche("\t\t=> Du coup on va appeler un résident : ", contrainteTauxResident)
# on fait la liste des voeux satisfaisant
# les deux contraintes à la fois, ordonnée par rang de classement
eligibles = [ ]
for queue in filesAttente.values():
if queue:
voeu = queue[-1] # le meilleur a été ajouté en dernier
if ((estBoursier(voeu) or not contrainteTauxBoursier)
and (estResident(voeu) or not contrainteTauxResident)):
eligibles.append(voeu)
affiche(" Les vœux satisfaisant les deux contraintes à la fois, ordonnés par rang de classement sont :\n", eligibles)
# stocke le meilleur candidat à appeler tout en respectant
# les deux contraintes si possible
# ou à défaut seulement la contrainte sur le taux boursier
meilleur = None
if len(eligibles) > 0:
# on prend le meilleur de cette liste
meilleur = max(eligibles, key=lambda voeu: -voeu["rang"])
affiche(" La liste des éligibles n'est pas vide, donc le-la meilleur-e est le-la meilleur-e de cette liste =", meilleur)
else:
# la liste peut être vide dans le cas où les deux contraintes
# ne peuvent être satisfaites à la fois.
# Dans ce cas nécessairement il y a une contrainte sur chacun des deux taux
# (donc au moins un boursier non encore sélectionné)
# et il ne reste plus de boursier résident,
# donc il reste au moins un boursier non résident
CandidatsBoursierNonResident = filesAttente[BoursierNonResident]
meilleur = max(CandidatsBoursierNonResident, key=lambda voeu: -voeu["rang"])
affiche(" La liste des éligibles est pas vide, donc le-la meilleur-e est le-la meilleur-e de la liste des boursier-e-s non résident-e-s =", meilleur)
# suppression du candidat choisi de sa file d'attente
saFileAttente = filesAttente[meilleur["type"]]
affiche(" On vérifie si le-la meilleur", meilleur, "est aussi le-la meilleur-e de sa liste contenant", len(saFileAttente), "candidat-e-s du type", meilleur['type'])
meilleur_de_sa_liste = saFileAttente.pop()
# ajout du meilleur candidat à l'ordre d'appel
ordreAppel.append(meilleur)
nbAppeles += 1
affiche(" On ajoute le meilleur", meilleur, "à l'ordre d'appel, c'est le-la", nbAppeles, "ème à être appelé-e.")
if estBoursier(meilleur):
nbBoursiersAppeles += 1
affiche(" En plus, c'est le-la", nbBoursiersAppeles, "ème boursier-e à être appelé-e.")
if estResident(meilleur):
nbResidentsAppeles += 1
affiche(" En plus, c'est le-la", nbResidentsAppeles, "ème résident-e à être appelé-e.")
# fin de la boucle while
affiche("\n3. On a terminé la boucle, on a rempli l'ordre d'appel.")
return ordreAppel
# ### Exemple (A1)
#
# Avec les valeurs prisent ci-dessus comme exemple :
# In[70]:
calculerOrdreAppel(
voeuxClasses,
tauxMinBoursiersPourcents,
tauxMinResidentsPourcents
)
# - Sur cet exemple, on constate que la contrainte sur les boursiers a pu aider le candidat Florian, qui étais classé 6ème (le meilleur parmi les boursiers) à remonter devant certains candidats non boursiers.
#
# - La suite est logique, entre Alice, Bob, Christophe et Dora, puis on voit que Guillaume est aussi passé devant d'autres candidats avec la contrainte de 20% de boursiers (après les 5 premiers candidats, dont un boursier, il faut ajouter Guillaume pour continuer à satisfaire la contrainte de minimum 20% de boursiers).
# ### Visualisation interactive
#
# Les morceaux suivants sont hors programme, *ne regardez pas le détail du code*.
#
# Ils vont permettre d'explorer interactivement l'influence des deux taux minimums (taux de boursiers-ères et résidents-entes) sur le résultat de l'ordre d'appel.
# In[15]:
from IPython.display import display
from ipywidgets import interact
# On définit la fonction que l'on souhaite explorer :
# In[16]:
def fait_fonctionATester(
voeuxClasses,
tauxMinBoursiersPourcents=20,
tauxMinResidentsPourcents=0
):
def fonctionATester(
tauxMinBoursiersPourcents=tauxMinBoursiersPourcents,
tauxMinResidentsPourcents=tauxMinResidentsPourcents
):
ordreAppel = calculerOrdreAppel(
voeuxClasses,
tauxMinBoursiersPourcents,
tauxMinResidentsPourcents,
afficheTout=False # on cache la sortie
)
res = [voeu["rang"] for voeu in ordreAppel]
return res
return fonctionATester
# Par exemple, si on demande $90%$ de boursiers, on voit que les candidats classés 6ème (Florian) et 7ème (Guillaume) sont mis en avant.
# In[17]:
fait_fonctionATester(voeuxClasses)(90, 0)
# Mais la visualisation interactive suivante permet de suivre l'influence des deux paramètres sur la liste des rangs finaux beaucoup plus facilement.
# In[18]:
interact(
fait_fonctionATester(voeuxClasses, tauxMinBoursiersPourcents=20, tauxMinResidentsPourcents=0),
tauxMinBoursiersPourcents=(0, 100, 1),
tauxMinResidentsPourcents=(0, 100, 1)
);
# > Ca ne marche pas sur ma machine, mais sur une installation neuve ou sur [MyBinder](http://mybinder.org/), tout fonctionne bien…
# ### Autres exemples (A2, A4)
#
# Le document de référence propose deux autres exemples de petite taille, notés A2 et A4.
# #### A2 : 2% de boursiers-ères
#
# Comme dans le document de référence : C1 C2 C3 C4 C5 B6 C7 C8, où B6 est boursier, et tous sont non résidents.
# (Plus besoin des noms dans cet exemple là, on les enlève)
# In[19]:
C1 = { "type": NonBoursierNonResident, "rang": 1 }
C2 = { "type": NonBoursierNonResident, "rang": 2 }
C3 = { "type": NonBoursierNonResident, "rang": 3 }
C4 = { "type": NonBoursierNonResident, "rang": 4 }
C5 = { "type": NonBoursierNonResident, "rang": 5 }
B6 = { "type": BoursierNonResident, "rang": 6 }
C7 = { "type": NonBoursierNonResident, "rang": 7 }
C8 = { "type": NonBoursierNonResident, "rang": 8 }
# In[20]:
voeuxClasses_A2 = [C1, C2, C3, C4, C5, B6, C7, C8]
# On reproduit l'exemple du document de référence, avant de vous laisser explorer l'influence des deux taux.
# In[21]:
fait_fonctionATester(voeuxClasses_A2)(2, 0)
# In[22]:
interact(
fait_fonctionATester(
voeuxClasses_A2, tauxMinBoursiersPourcents=2, tauxMinResidentsPourcents=0
),
tauxMinBoursiersPourcents=(0, 100, 1),
tauxMinResidentsPourcents=(0, 100, 1)
);
# On remarque que même un minimum de $2%$ de boursiers-ères suffit à faire remonter le candidat B6 en tête.
#
# L'ordre des rangs est respecté seulement si on a aucune contrainte sur le taux de boursiers.
# In[23]:
fait_fonctionATester(voeuxClasses_A2)(0, 0)
# #### A4 : 10% de boursiers-ères
#
# Comme dans le document de référence : C1 B2 B3 C4 C5 C6 C7 B8 C9 C10, où B2 B3 et B8 sont boursiers et tous sont non résidents.
# In[24]:
C1 = { "type": NonBoursierNonResident, "rang": 1 }
B2 = { "type": BoursierNonResident, "rang": 2 }
B3 = { "type": BoursierNonResident, "rang": 3 }
C4 = { "type": NonBoursierNonResident, "rang": 4 }
C5 = { "type": NonBoursierNonResident, "rang": 5 }
C6 = { "type": NonBoursierNonResident, "rang": 6 }
C7 = { "type": NonBoursierNonResident, "rang": 7 }
B8 = { "type": BoursierNonResident, "rang": 8 }
C9 = { "type": NonBoursierNonResident, "rang": 9 }
C10 = { "type": NonBoursierNonResident, "rang": 10 }
# In[25]:
voeuxClasses_A4 = [C1, B2, B3, C4, C5, C6, C7, B8, C9, C10]
# On reproduit l'exemple du document de référence, avant de vous laisser explorer l'influence des deux taux.
# In[26]:
fait_fonctionATester(voeuxClasses_A4)(10, 0)
# In[27]:
interact(
fait_fonctionATester(
voeuxClasses_A4, tauxMinBoursiersPourcents=10, tauxMinResidentsPourcents=0
),
tauxMinBoursiersPourcents=(0, 100, 1),
tauxMinResidentsPourcents=(0, 100, 1)
);
# ### Visualisations colorées
# On va utiliser [ipythonblocks](http://www.ipythonblocks.org/).
# In[28]:
from ipythonblocks import BlockGrid
# In[29]:
couleursTypeCandidat = {
NonBoursierNonResident: (200, 200, 200), # gris
NonBoursierResident: (0, 0, 200), # bleu
BoursierNonResident: (200, 0, 0), # rouge
BoursierResident: (200, 0, 200), # violet
}
# In[30]:
def voirListeVoeu(voeuxClasses):
nbVoeux = len(voeuxClasses)
grille = BlockGrid(nbVoeux, 1, fill=(200, 200, 200))
for i, voeu in enumerate(voeuxClasses):
typeCandidat = voeu["type"]
couleur = couleursTypeCandidat[typeCandidat]
grille[0, i].set_colors(*couleur)
return grille
# In[31]:
voeuxClasses_A4
# In[32]:
voirListeVoeu(voeuxClasses_A2)
# In[33]:
voirListeVoeu(voeuxClasses_A4)
# Les candidats-ates boursiers-ères sont en rouges. On peut visualiser que le calcul de l'ordre d'appel va faire remonter un-e candidat-e boursier-ère :
# In[34]:
voirListeVoeu(calculerOrdreAppel(voeuxClasses_A2, 20, 20, False))
# In[35]:
voirListeVoeu(calculerOrdreAppel(voeuxClasses_A4, 20, 20, False))
# ### Entrée aléatoire
#
# Pour des exemples plus complets, on génère ici une liste de vœux de taille fixée, où chaque vœu sera aléatoirement boursier ou non, résident ou non.
# Cela va permettre de mieux comprendre l'algorithme sur des entrées de taille plus grande.
# In[36]:
import random
import math
# In[62]:
def fait_voeuxClasses_aleatoire(
taille=100,
tauxBoursiers=20,
tauxResidents=20,
random_seed=0,
):
random.seed(random_seed)
# on commence par avoir une liste triée de non boursier non résident
voeuxClasses = [
{'type': NonBoursierNonResident, 'rang': i}
for i in range(1, taille + 1)
]
# pour certains candidats aléatoires on les transforme en boursier et/ou en résident
nbBoursiers = int(math.ceil(taille * tauxBoursiers / 100.0))
for voeu in random.sample(voeuxClasses, nbBoursiers):
voeu['type'] = BoursierNonResident
nbResidents = int(math.ceil(taille * tauxResidents / 100.0))
for voeu in random.sample(voeuxClasses, nbResidents):
if estBoursier(voeu): voeu['type'] = BoursierResident
else: voeu['type'] = NonBoursierResident
# on la mélange enfin et on la renvoie
random.shuffle(voeuxClasses)
return voeuxClasses
# In[63]:
voeuxClasses_exemple = fait_voeuxClasses_aleatoire(
70,
tauxBoursiers=20, # ~14 boursiers
tauxResidents=20 # ~14 résidents
)
# On va avoir en moyenne 14 boursiers-ères et 14 résidents-entes, avec quelques intersections.
# In[64]:
voirListeVoeu(voeuxClasses_exemple)
# In[65]:
voeuxClasses_exemple_sortie = calculerOrdreAppel(
voeuxClasses_exemple,
tauxMinBoursiersPourcents=100,
tauxMinResidentsPourcents=100,
afficheTout=False
)
# In[66]:
voirListeVoeu(voeuxClasses_exemple_sortie)
# On visualise assez bien : avec une contrainte un peu folle qui impose de prendre 100% des boursiers-ères (en rouge ou violet) et 100% des résidents (en bleu ou violet), l'algorithme d'appel a d'abord pris les candidats de types `BoursierResident` (violet), puis `BoursierNonResident` (rouge) car le décret officiel demande de favoriser les boursiers, puis les `NonBoursierResident` (bleu) et enfin les autres.
# ### Une visualisation interactive plus complète
# On peut combiner la visualisation interactive et la visualisation avec des couleurs.
# On va d'abord avoir une
# In[67]:
def fait_fonctionATester_couleurs(
taille=70,
tauxBoursiers=20,
tauxResidents=20,
random_seed=0,
tauxMinBoursiersPourcents=10,
tauxMinResidentsPourcents=0
):
def fonctionATester(
taille=taille,
tauxBoursiers=tauxBoursiers,
tauxResidents=tauxResidents,
random_seed=random_seed,
tauxMinBoursiersPourcents=tauxMinBoursiersPourcents,
tauxMinResidentsPourcents=tauxMinResidentsPourcents
):
voeuxClasses = fait_voeuxClasses_aleatoire(
taille,
tauxBoursiers=tauxBoursiers,
tauxResidents=tauxResidents,
random_seed=random_seed
)
print("Visualisation de l'algorithme de calcul de l'ordre d'appel.")
print("1. Vœux non triés par rang :")
display(voirListeVoeu(voeuxClasses))
print("2. Vœux triés par rang, mais pas par l'algorithme :")
voeuxClasses_tries = sorted(voeuxClasses, key=lambda voeu: -voeu["rang"])
display(voirListeVoeu(voeuxClasses_tries))
print("3. Vœux triés par l'algorithme :")
ordreAppel = calculerOrdreAppel(
voeuxClasses,
tauxMinBoursiersPourcents,
tauxMinResidentsPourcents,
afficheTout=False # on cache la sortie
)
display(voirListeVoeu(ordreAppel))
return fonctionATester
# In[68]:
interact(
fait_fonctionATester_couleurs(
taille=70,
tauxBoursiers=20,
tauxResidents=20,
random_seed=0,
tauxMinBoursiersPourcents=10,
tauxMinResidentsPourcents=0
),
taille=(1, 500, 1),
tauxBoursiers=(0, 100, 1),
tauxResidents=(0, 100, 1),
random_seed=(0, 1000, 1),
tauxMinBoursiersPourcents=(0, 100, 1),
tauxMinResidentsPourcents=(0, 100, 1)
);
# ### Focalisation sur un candidat
#
# Jusqu'ici on a visualisé l'ordre d'appel de toute la liste.
# On va se focaliser sur un seul candidat pour voir l'influence du comportement de tous les paramètres sur son ordre d'appel final.
# In[114]:
def voirListeVoeu_avecFocus(voeuxClasses, randDuFocus):
grille = voirListeVoeu(voeuxClasses)
for i, voeu in enumerate(voeuxClasses):
if voeu['rang'] != randDuFocus:
r, g, b = grille[0, i].rgb
grille[0, i].rgb = (max(0, int(r*0.65)), max(0, int(g*0.65)), max(0, int(b*0.65)))
return grille
# In[115]:
def fait_fonctionATester_unSeulFocus(
taille=70,
tauxBoursiers=20,
tauxResidents=20,
random_seed=0,
rangDuFocus=1,
focusEstBoursier=False,
focusEstResident=False,
tauxMinBoursiersPourcents=10,
tauxMinResidentsPourcents=0
):
def fonctionATester(
taille=taille,
tauxBoursiers=tauxBoursiers,
tauxResidents=tauxResidents,
random_seed=random_seed,
rangDuFocus=1,
focusEstBoursier=False,
focusEstResident=False,
tauxMinBoursiersPourcents=tauxMinBoursiersPourcents,
tauxMinResidentsPourcents=tauxMinResidentsPourcents
):
voeuxClasses = fait_voeuxClasses_aleatoire(
taille,
tauxBoursiers=tauxBoursiers,
tauxResidents=tauxResidents,
random_seed=random_seed
)
for voeu in voeuxClasses:
if voeu['rang'] == rangDuFocus:
if focusEstBoursier and focusEstResident:
voeu['type'] = BoursierResident
elif focusEstBoursier and not focusEstResident:
voeu['type'] = BoursierNonResident
elif not focusEstBoursier and focusEstResident:
voeu['type'] = NonBoursierResident
else:
voeu['type'] = NonBoursierNonResident
print("Visualisation de l'algorithme de calcul de l'ordre d'appel.")
print("1. Vœux non triés par rang :")
display(voirListeVoeu_avecFocus(voeuxClasses, rangDuFocus))
print("2. Vœux triés par rang, mais pas par l'algorithme :")
voeuxClasses_tries = sorted(voeuxClasses, key=lambda voeu: voeu["rang"])
display(voirListeVoeu_avecFocus(voeuxClasses_tries, rangDuFocus))
print("3. Vœux triés par l'algorithme :")
ordreAppel = calculerOrdreAppel(
voeuxClasses,
tauxMinBoursiersPourcents,
tauxMinResidentsPourcents,
afficheTout=False # on cache la sortie
)
display(voirListeVoeu_avecFocus(ordreAppel, rangDuFocus))
return fonctionATester
# In[116]:
# https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html#Arguments-that-are-dependent-on-each-other
import ipywidgets
taille_widget = ipywidgets.IntSlider(min=1, max=200, step=1, value=10)
rang_widget = ipywidgets.IntSlider(min=1, max=200, step=1, value=1)
def update_taille_max(*args):
rang_widget.max = taille_widget.value
rang_widget.observe(update_taille_max, 'value')
# In[117]:
interact(
fait_fonctionATester_unSeulFocus(
taille=70,
tauxBoursiers=20,
tauxResidents=20,
random_seed=0,
rangDuFocus=1,
focusEstBoursier=False,
focusEstResident=False,
tauxMinBoursiersPourcents=10,
tauxMinResidentsPourcents=0
),
taille=taille_widget,
tauxBoursiers=(0, 100, 1),
tauxResidents=(0, 100, 1),
random_seed=(0, 1000, 1),
rangDuFocus=rang_widget,
focusEstBoursier=False,
focusEstResident=False,
tauxMinBoursiersPourcents=(0, 100, 1),
tauxMinResidentsPourcents=(0, 100, 1)
);
# ----
# ## Algorithme 2 : Calcul des propositions
# In[73]:
from IPython.display import HTML
# In[74]:
HTML("{}".format('TODO '*54))
# ## Conclusion
#
# Ce petit notebook n'est pas terminé, c'est un test *en cours de rédaction*.