Épreuve d'informatique CAPES 2018

by Joseph Razik, on 2019-10-18
capes_2018

Sujet du CAPES 2018, épreuve d'informatique

Source: http://www4.ac-nancy-metz.fr/capesmath/data/uploads/EP1_info_2018.pdf

N.B. : Ceci ne représente ni LA solution ni LE corrigé de cette épreuve mais uniquement la façon avec laquelle je répondrais à une telle série d'exercices dans le cadre de cette épreuve.

Problème 1 - suite de Lucas

In [1]:
# importation de quelques fonctions que je vais utiliser
from math import ceil, floor, log, sqrt, log2
In [2]:
# illustration des deux fonction d'arrondi présentées dans le sujet: ceil (arrondi supérieur) et floor (arrondi inférieur)
# arrondi sup.
print(ceil(1.24)) 
print(ceil(2.0))
print(ceil(2))

# arrondi inf.
print(floor(1.24))
print(floor(1.0))
print(floor(1))
2
2
2
1
1
1

La suite $(L_n)_{n \geq 0}$ des nombres de Lucas est définie par récurrence ainsi :

$\left\{ \begin{array}{ccl} L_0 & = & 2 \\ L_1 & = & 1 \\ L_n & = & L_{n-1} + L_{n-2} \quad pour \quad n \geq 2 \end{array} \right.$

Question 1

Calculer $L_2$, $L_3$, $L_4$, $L_5$, $L_6$ et $L_7$.

In [3]:
# définition d'une fonction simple reprenant strictement la relation énoncée précédemment
def L_n(n):
    """ Calcul le nombre de Lucas L(n) """
    if n == 0:
        return 2
    elif n == 1:
        return 1
    else:
        # petit calcul récursif pour les éléments n ≥ 2
        return L_n(n - 1) + L_n(n - 2)
In [4]:
# Les éléments demandés
[L_n(i) for i in range(2, 8)]
Out[4]:
[3, 4, 7, 11, 18, 29]

Question 6.1

On propose la fonction suivante pour calculer le n-ième nombre de Lucas. On rappelle que ** est l'opérateur Python d'élévation à la puissance.

In [5]:
def lucas1(n):
    if n == 0:
        return 2
    phi = (1 + sqrt(5)) / 2
    phi2 = (1 - sqrt(5)) / 2
    return phi**n + phi2**n
In [6]:
# calcul des 8 premières valeurs, comme précédemment
[lucas1(n) for n in range(8)]
Out[6]:
[2,
 1.0,
 3.0,
 4.0,
 7.000000000000001,
 11.000000000000002,
 18.000000000000004,
 29.000000000000007]

Nous n'obtenons pas des entiers car $\phi$ et $\hat{\phi}$ sont des nombres réels qui, sur un ordinateur, on une représentation inexacte en base 2 (codage des flottant IEEE 754 sur 32 ou 64 bits). Plus $n$ va être grand, plus l'erreur cumulée va être grande.

Question 6.2

On propose maintenant la fonction suivante pour calculer le n-ième nombre de Lucas:

In [7]:
def lucas2(n):
    if n == 0:
        return 2
    phi = (1+ sqrt(5)) / 2
    if n % 2 == 0:
        return ceil(phi**n)
    else:
        return floor(phi**n)
In [8]:
# calcul des 8 premières valeurs
[lucas2(n) for n in range(8)]
Out[8]:
[2, 1, 3, 4, 7, 11, 18, 29]

L'utilisation des fonctions ceil et floor permet de projeter dans l'espace des entiers les valeurs réelles finales calculées.

Par ailleurs, il y a un phénomène de quantification.

Je pense que le choix des fonctions des lignes 6 à 9 vient du fait que $|\hat\phi| < 1$, et que donc $|\hat\phi|^n$ tend vers 0.

Si $n$ est pair, elle tend vers 0 de manière positive, si $n$ est impair, elle tend vers 0 de manière négative.

Ainsi dans un cas il faut minorer et dans l'autre majorer.

Question 6.3

Un calcul exact montre que $L_{36} = 33385282$, mais lucas2(36) renvoie la valeur 33385283.

Comment expliquez-vous cela ?

In [9]:
lucas2(36)
Out[9]:
33385283

Or $L_{36} = 33385282$. L'erreur vient encore une fois du fait que la représentation et le calcul des réels n'est pas exacte. Le cumul des erreurs réussit à incrémenter de 1 la valeur théorique. La précision de la représentation IEEE 754 des flottants sur 32 bits est de l'ordre de 7 chiffres significatifs. Or ici, $n=36$ donc $L_{36} \geq 10^7$ (cf. question 5). Il y a donc un risque fort d'erreur sur le chiffre des unités du nombre que l'on va calculer.

Question 7.1

On souhaite écrire une nouvelle fonction de calcul des termes de la suite de Lucas.

Pour éviter tout problème lié au calcul avec des flottants, on souhaite ne travailler qu'avec des entiers, sur lesquels Python calcule de manière exacte.

Recopier et compléter la fonction suivante, qui renvoie la valeur de $L_n$.

In [10]:
def lucas3(n):
    if n == 0:
        return 2
    if n == 1:
        return 1
    a, b = (2, 1)
    for i in range(n):
        a, b = (b, a + b)
    return a
In [11]:
# calcul des 8 premiers nombrer de Lucas avec cette nouvelle fonction
[lucas3(n) for n in range(8)]
Out[11]:
[2, 1, 3, 4, 7, 11, 18, 29]

Question 7.2

Déterminer un invariant de boucle qui précise, en fonction de la valeur de i, les valeurs des variables a et b avant et après l'exécution de la ligne 7 et en déduire que lucas3 renvoie un résultat exact.

N'ayant à chaque étape qu'une opération élémentaire d'addition sur des entiers, il n'y a pas de passage intermédiaire par des flottants. Le calcul sera donc exact.

QuestionÇ 7.3

Pour $n \geq 2$, combien d'additions sont effectuées par lucas3(n) ?

Pour $n \geq 2$, il est effectué 1 addition par pas de la boucle, et il y a $n$ pas. Donc au total $n$ additions.

Question 9.1

Si $k$ est un entier, que calcule l'expression Python suivante : 1 - 2*(k % 2) ?

On rappelle que l'opérateur % calcule le reste dans la division entière : 7 % 3 vaut 1 ; 8 % 3 vaut 2 et 9 % 3 vaut 0.

In [12]:
# petit rappel sur l'opérateur  %
print(7 % 3, 8 % 3, 9 % 3)
1 2 0
In [13]:
# k % 2 est à valeur dans {0, 1}, donc 1-2*(k%2) vaudra 1 si k est pair, 1-2=-1 si k est impair
[1 - 2*(k % 2) for k in range(10)]
Out[13]:
[1, -1, 1, -1, 1, -1, 1, -1, 1, -1]
In [14]:
# l'expression précédente est équivalente à classique (-1)**k
[(-1)**k for k in range(10)]
Out[14]:
[1, -1, 1, -1, 1, -1, 1, -1, 1, -1]

Question 9.2

Recopier et compléter la fonction récursive suivante, de sorte que lucas4(n) renvoie le couple $(L_n , L_{n+1})$ .

In [15]:
def lucas4(n):
    if n == 0:
        return (2, 1)
    if n == 1:
        return (1, 3)
    k = n // 2
    u = 1 - 2*(k % 2)
    a, b = lucas4(k)
    if n % 2 == 0:
        return (a**2 - 2*u, a*b - u)
    else:
        return (a*b - u , b*b + 2*u)
In [16]:
# calcul sur les 10 premières valeur entières
[lucas4(k) for k in range(10)]
Out[16]:
[(2, 1),
 (1, 3),
 (3, 4),
 (4, 7),
 (7, 11),
 (11, 18),
 (18, 29),
 (29, 47),
 (47, 76),
 (76, 123)]

Question 9.3

Pour tout entier $n \geq 2$, exprimer en fonction de $n$ le nombre d'appels récursifs que réalise lucas4(n) .

Pour $n \geq 2$, la fonction appelle un fois récursivement avec la valeur n // 2. Ainsi, à chaque étape, $n$ est «divisé» par 2.

Au final, il y aura $log_2(n)$ appels récursifs, et pour être plus précis $\lfloor log_2(n) \rfloor$.

In [17]:
# n = 3
print(floor(log2(3)))  # il y a un appel récursif

# n = 4
print(floor(log2(4)))  # il y a 2 appels récursifs
1
2

Question 10

Pour $n$ un entier naturel, on considère le vecteur colonne de $\mathbb{R}^2$ défini par $V_n = \left( \begin{array}{c} L_n \\ L_{n+1} \end{array} \right) $

On considère également la matrice $A = \left(\begin{array}{cc} 0 & 1 \\ 1 & 1 \end{array}\right)$

Pour tout entier $n \geq 1$ , exprimer $V_n$ en fonction de $A$ et de $V_{n−1}$, puis exprimer $V_n$ en fonction de $A$, de $n$ et de $V_0$.

On représente dans la suite une matrice carrée $\left(\begin{array}{cc}a & b \\ c & d \end{array}\right)$ par la liste de deux listes [[a, b], [c, d]].

On montre par récurrence que $V_{n} = A \times V_{n-1}$ et donc $V_n = A^n \times V_0$

Question 11.1

Écrire en Python une fonction prodMat(M1, M2) qui prend en arguments deux matrices $2 \times 2$ représentées par des listes comme indiqué ci-dessus, et qui renvoie la liste qui représente la matrice produit $M1 \times M2$.

In [18]:
def prodMat(M1, M2):
    """ Renvoie le produit de 2 matrice 2x2  """
    return [
        [M1[0][0]*M2[0][0] + M1[0][1]*M2[1][0],
         M1[0][0]*M2[0][1] + M1[0][1]*M2[1][1]],
        [M1[1][0]*M2[0][0] + M1[1][1]*M2[1][0],
         M1[1][0]*M2[0][1] + M1[1][1]*M2[1][1]]
    ]
In [19]:
# un petit test de la fonction
prodMat([[1, 2], [3, 4]], [[5, 6],[7, 8]])
Out[19]:
[[19, 22], [43, 50]]

Question 11.2

Combien l'appel prodMat(M1, M2) effectue-t-il d'additions ? de multiplications ?

La fonction prodMat effectue pour chacun des coefficients 2 multiplications et 1 addition. Donc au total: $4 \times 2 = 8$ multiplications et $4 \times 1 = 4$ additions.

Question 11.3

On considère la fonction (naïve) d'élévation d'une matrice à une puissance entière suivante.

Combien l'appel puissanceMat(A, p) réalise-t-il d'appels à prodMat en fonction de $p$ ?

In [20]:
def puissanceMat(M, p):
    R = [[1, 0], [0, 1]]
    for i in range (p):
        R = prodMat(R, M)
    return R

La fonction puissanceMat fait $p$ appels à la fonction prodMat.

Question 12.1

On propose la fonction suivante qui implémente l'exponentiation rapide pour les matrices $2 \times 2$ .

In [21]:
def puissanceMatRapide(M, n):
    R = [[1, 0], [0, 1]]
    P = M
    while n > 0:
        if n % 2 == 1:
            R = prodMat(R, P)
        P = prodMat(P, P)
        n = n // 2
    return R

Montrer que par cette méthode, le nombre d'appel à la fonction prodMat est majoré par $2 + 2\lfloor log_2 n \rfloor$ .

La boucle while divise $n$ par 2 à chaque étape jusqu'à atteindre 0. Il y aura donc $\lfloor log_2n \rfloor + 1$ étapes de boucle. Au pire à chaque étape, il y a 2 appels à prodMat. Donc au final, il y a au plus $2 + 2\lfloor log_2 n \rfloor$ appels à prodMat.

Question 12.2

Exprimer en fonction de $M$ et de $i$ la valeur de la matrice $P$, après la ième itération de la boucle while .

Après la ième itération de la boucle while, $P$ aura effectué $i$ fois l'opération $P = P^2$. Ainsi, $P$ vaudra $P = ((((M^2)^2)^2)...)^2$ $i$ fois, c'est-à-dire $M^{2^i}$.

Question 13

Proposer le code d'une fonction lucas5(n) qui retourne la valeur du nombre de Lucas $L_n$ en utilisant la fonction puissanceMatRapide.

In [22]:
def lucas5(n):
    """ calcul le nombre de Lucas en utilisant la représentation matricielle.
        V_n = (L_n, L_n+1) = A^n x V_0
    """
    # initialisation des variables
    V0 = [2, 1]  # (L_0, L_1)
    A = [[0, 1], [1, 1]]
    
    # on calcul V_{n-1} = A^{n-1} x V_0
    A_n = puissanceMatRapide(A, n - 1)  # on s'arrête un grand avant, c'est suffisant
    V_n = [A_n[0][0] * V0[0] + A_n[0][1]*V0[1], A_n[1][0]*V0[0] + A_n[1][1]*V0[1]]
    
    # comme en fait on a calculé V_{n-1}, on prend le deuxième coefficient
    return V_n[1]
In [23]:
lucas5(36)
Out[23]:
33385282

Problème 2 - emplois du temps et graphes d'intervalles

In [24]:
# importatior de quelques modules pour cette partie, pour les graphiques
%pylab inline
Populating the interactive namespace from numpy and matplotlib
In [25]:
def affiche(liste_intervalles):
    """ fonction qui affiche les graphes d'intervalles.  """
    figure()
    rc('text', usetex=True)
    ylim((-0.25, len(liste_intervalles) - 0.25))
    t_min = min(map(lambda x: x[0], liste_intervalles))
    t_max = max(map(lambda x: x[1], liste_intervalles))
    for i, intervalle in enumerate(liste_intervalles):
        plot(intervalle, [i]*2, "k")
        text(intervalle[0], i + 0.10, "$C_" + str(i) + "$", {'fontsize': 20})
In [26]:
# on défini deux exemples de liste de cours
# exemple 1
intervalles_1 = [[0, 7], [2, 13], [8, 10]]
affiche(intervalles_1)

# exemple 2
intervalles_2 = [[0, 2], [1, 7], [4, 11], [5, 6], [8, 11], [9, 13]]
affiche(intervalles_2)

Question 2.2

Compléter la définition suivante : def insere(l, elt):l est une liste d'entiers triée dans l'ordre croissant et elt est un entier, et qui renvoie une liste triée obtenue par insertion à sa place de elt dans l. On notera que la liste l peut être vide.

In [27]:
# version qui utilise peu certains raccoucis du langage python
def insere(l, elt):
    """ Insere elt dans une copie de la liste l et la retourne """
    # l est supposée triée croissante
    idx = 0
    nouvelle_liste = []
    while idx < len(l):
        e = l[idx]
        if e < elt:
            # on ajoute les éléments plus petits
            nouvelle_liste.append(e)
        else:
            break
        idx = idx + 1
    nouvelle_liste.append(elt)  # on ajoute l'élément elt
    nouvelle_liste.extend(l[idx:])  # on complète avec le reste des éléments
    return nouvelle_liste        
In [28]:
# quelques tests pour vérifier que cela fonctionne
insere([], 2)
Out[28]:
[2]
In [29]:
insere([1, 2, 4, 5], 3)
Out[29]:
[1, 2, 3, 4, 5]
In [30]:
insere([1, 2, 3, 4, 5], 3) 
Out[30]:
[1, 2, 3, 3, 4, 5]
In [31]:
insere([1, 2, 3], 0)
Out[31]:
[0, 1, 2, 3]

On suppose pour la suite que l'on dispose d'une fonction similaire insereBis qui opère sur des listes de listes plutôt que sur des listes d'entiers, le critère de tri étant l'ordre des premiers éléments de chaque sous-liste. Plus précisément, la fonction insereBis a pour en-tête def insereBis(LL, li): et a pour effet d'insérer une liste li à la place adéquate dans la liste de listes LL.

In [32]:
# Il n'est pas précisé dans le texte le comportement à adopter si deux listes possèdent le même premier élément

def insereBis(LL, li):
    """ Insere la liste li dans une copie de la liste de liste LL, par rapport au premier élément des listes. """
    # le code est très similaire à celui de la fonction insere précédente.
    idx = 0
    nouvelle_liste = []
    while idx < len(LL):
        e = LL[idx]
        # on compare le premier élément de la liste li et d'une des listes de LL
        if e[0] < li[0]:
            nouvelle_liste.append(e)
        else:
            break
        idx = idx + 1
    nouvelle_liste.append(li)
    nouvelle_liste.extend(LL[idx:])
    return nouvelle_liste
    
In [33]:
# quelques tests
insereBis([], [1, 2])
Out[33]:
[[1, 2]]
In [34]:
insereBis([[2, 3, 4], [5, 6, 7]], [3, 4])
Out[34]:
[[2, 3, 4], [3, 4], [5, 6, 7]]
In [35]:
insereBis([[4, 5], [5, 6], [6, 7]], [1, 2, 3])
Out[35]:
[[1, 2, 3], [4, 5], [5, 6], [6, 7]]
In [36]:
insereBis([[1, 2], [2, 3], [3, 4]], [2, 4])
Out[36]:
[[1, 2], [2, 4], [2, 3], [3, 4]]

Question 3.1

Pour automatiser l'allocation des salles, on va utiliser une autre modélisation. L'intervalle [deb, fin[ représentant le cours numéro i va être représenté par deux événements, modélisés par des listes de longueur 3 : [deb,i,0] et [fin,i,1]. Une liste d'intervalles pourra ainsi être représentée par une liste d'événements, c'est-à -dire une liste de listes de longueur 3 de la forme [instant,num,0 ou 1].

Compléter la définition def traduit(liste_intervalles): qui prend en argument une liste d'intervalles représentés par des listes de longueur 2 et qui renvoie une liste d'événements (listes de longueur 3) correspondante. Notons que pour $n$ intervalles, on obtient $2n$ événements.

In [37]:
# par rapport à la description d'un événement, le codage suivant est utilisé :
# la valeur 0 du tuple indique un début de cours, la valeur 1 une fin de cours.

def traduit(liste_intervalles):
    """ Traduit une liste d'intervalles en liste d'événements. """
    liste_evt = []
    for i, intervalle in enumerate(liste_intervalles):
        liste_evt.append([intervalle[0], i, 0])
        liste_evt.append([intervalle[1], i, 1])
    return liste_evt
In [38]:
traduit(intervalles_1)
Out[38]:
[[0, 0, 0], [7, 0, 1], [2, 1, 0], [13, 1, 1], [8, 2, 0], [10, 2, 1]]
In [39]:
traduit(intervalles_2)
Out[39]:
[[0, 0, 0],
 [2, 0, 1],
 [1, 1, 0],
 [7, 1, 1],
 [4, 2, 0],
 [11, 2, 1],
 [5, 3, 0],
 [6, 3, 1],
 [8, 4, 0],
 [11, 4, 1],
 [9, 5, 0],
 [13, 5, 1]]

Question 3.2

Pour l'efficacité des algorithmes de résolution, on va travailler sur une liste d'événements triée par instants croissants. On appellera agenda toute liste d'événements triés par instants croissants. Quels sont les agendas correspondant aux exemples 1 et 2 ?

In [40]:
# pour accélérer un peu, on peut obtenir les événements triés ainsi
sorted(traduit(intervalles_1), key=lambda x: x[0])
Out[40]:
[[0, 0, 0], [2, 1, 0], [7, 0, 1], [8, 2, 0], [10, 2, 1], [13, 1, 1]]
In [41]:
sorted(traduit(intervalles_2), key=lambda x: x[0])
Out[41]:
[[0, 0, 0],
 [1, 1, 0],
 [2, 0, 1],
 [4, 2, 0],
 [5, 3, 0],
 [6, 3, 1],
 [7, 1, 1],
 [8, 4, 0],
 [9, 5, 0],
 [11, 2, 1],
 [11, 4, 1],
 [13, 5, 1]]

Question 3.3

Compléter la définition def agenda(liste_evt): qui prend en argument une liste d'événements (listes de longueur 3) obtenue par un appel à la fonction traduit et qui renvoie l'agenda correspondant. On pourra utiliser la fonctions insereBis.

In [42]:
# Soit on fait comme juste au dessus avec sorted, soit on le fait à la main
# je pense qu'il est attendu de le faire à la main, surtout avec l'indication d'utiliser insereBis
def agenda(liste_evt):
    result = []
    # on insere les éléments un par un à partir d'une liste vide au départ
    for elt in liste_evt:
        result = insereBis(result, elt)
    return result
In [43]:
# exemple 1
A = traduit(intervalles_1)
print(A)
agenda(A)
[[0, 0, 0], [7, 0, 1], [2, 1, 0], [13, 1, 1], [8, 2, 0], [10, 2, 1]]
Out[43]:
[[0, 0, 0], [2, 1, 0], [7, 0, 1], [8, 2, 0], [10, 2, 1], [13, 1, 1]]
In [44]:
# exemple 2
B = traduit(intervalles_2)
agenda(B)
Out[44]:
[[0, 0, 0],
 [1, 1, 0],
 [2, 0, 1],
 [4, 2, 0],
 [5, 3, 0],
 [6, 3, 1],
 [7, 1, 1],
 [8, 4, 0],
 [9, 5, 0],
 [11, 4, 1],
 [11, 2, 1],
 [13, 5, 1]]

Question 4.1

On a demandé à des élèves d'écrire une fonction qui vérifie qu'une liste d'événements donnée contient bien autant de fin que de début, et dans le bon ordre, mais sans tester l'appariemment cours par cours, c'est-à -dire sans considérer les numéros d'intervalles.

Parmi les quatres solutions proposées, déterminer (sans justifier) la ou les réponses correctes.

  • ValideA est incorrecte: il peut y avoir plus de débuts que de fins

  • ValideB semble correcte: gère l'ordre et à la fin on doit bien avoir c == 0

  • ValideC est incorrecte: si on ne fait que des fins, la réponse est True

  • ValideD semble correcte: gère le c == 0 et l'ordre

Question 4.2

Adapter une des fonctions précédentes pour écrire une fonction intersection_max qui à partir d'un agenda calcule le nombre maximal d'intervalles qui s'intersectent mutuellement. Justifier la correction de l'approche.

In [45]:
# je prend la version valideD comme fonction de départ
def valideD(agenda):
    c = 0
    for e in agenda:
        c += 1 - 2*e[2]
        if c < 0:
            return False
    return c == 0
In [46]:
# je la modifie pour prendre en compte l'information du nombre d'intersection maximal
def intersection_max(agd):
    c = 0
    m = 0  # le max
    for e in agd:
        c += 1 - 2*e[2]
        m = max(m, c)
        if c < 0:
            return None
    return m
In [47]:
# exemple 1
intersection_max(agenda(A))
Out[47]:
2
In [48]:
# exemple 2
intersection_max(agenda(B))
Out[48]:
3

Question 4.3

En utilisant les fonctions précédentes, écrire une fonction nbr_optimal qui à partir d'une liste d'intervalles (modélisation initiale), calcule le nombre de salles nécessaire.

In [49]:
def nbr_optimal(liste_intervalles):
    evt = traduit(liste_intervalles)
    agd = agenda(evt)
    if valideD(agd):
        return(intersection_max(agd))
    return None
In [50]:
# pour l'exemple 1
nbr_optimal(intervalles_1)
Out[50]:
2
In [51]:
# pour l'exemple 2
nbr_optimal(intervalles_2)
Out[51]:
3

Question 5.1

On a demandé à un élève d'écrire une fonction qui, étant donné une liste de booléens, calcule le plus petit entier $i$ tel que la case d'indice $i$ vaille True. La fonction renverra $−1$ si un tel indice n'existe pas. Corriger sa fonction.

def plus_petit_vrai(liste):
    n = len(liste)                       (1)
    while liste[i] and (i < n): i += 1   (2)(3)
    if i = n: return -1                  (4)
    else: return i
In [52]:
# Premier bémol: conformité pep8, mais bon
# (1): pas d'initialisation de la variable "i"
# (2): la boucle va faire une erreur pour l'après dernier élément. En effet, le test liste[i] est effectué en premier
#      et donc si on a débordé, il y aura une erreur immédiatemente, sans même faire le deuxiène test. En inversant les
#      deux tests c'est bon
# (3): le test liste[i] est inversé: il faut boucler tant que le contenu est False
# (4): erreur de confusion "=" (affectation) et "==" (test)
#
# on obtient au final:
def plus_petit_vrai(liste):
    n = len(liste)
    i = 0
    while (i < n) and not liste[i]:
        i += 1
    if i == n:
        return -1
    else:
        return i
In [53]:
# Quelques tests
plus_petit_vrai([True, True])
Out[53]:
0
In [54]:
plus_petit_vrai([False, False])
Out[54]:
-1
In [55]:
plus_petit_vrai([])
Out[55]:
-1
In [56]:
plus_petit_vrai([False, True])
Out[56]:
1
In [57]:
plus_petit_vrai([True, False])
Out[57]:
0

Question 5.2

On utilise à chaque instant une liste de booléens pour indiquer si une salle est disponible ou non. En utilisant les fonctions précédentes, compléter la fonction suivante qui étant donnée une liste d'intervalles (listes de longueur 2), calcule une liste d'allocations des salles, toujours en allouant la salle disponible avec le plus petit numéro. Dans cette liste, la case d'indice le numéro du cours contient le numéro de la salle allouée.

In [58]:
def allocation(liste_intervalles):
    """ retourne le numéro de la salle allouée au cours i. """
    nb_cours = len(liste_intervalles)
    liste = agenda(traduit(liste_intervalles))
    nb_salles = nbr_optimal(liste_intervalles)  # nb salle max
    salles_dispos = [True] * nb_salles
    alloc = [-1] * nb_cours
    for l in liste:
        if l[2] == 0:
            alloc[l[1]] = plus_petit_vrai(salles_dispos)  # on cherche la première salle dispo
            salles_dispos[plus_petit_vrai(salles_dispos)] = False  # on l'occupe (plus disponible)
        else:
            salles_dispos[alloc[l[1]]] = True  # le cours est fini, on rend disponible la salle 
    return alloc
In [59]:
# pour l'exemple 1
allocation(intervalles_1)
Out[59]:
[0, 1, 0]
In [60]:
# pour l'exemple 2
allocation(intervalles_2)
Out[60]:
[0, 1, 0, 2, 1, 2]
In [61]:
def affiche_allocation(liste_intervalles):
    """ fonction qui va afficher l'allocation de salle, chaque salle a une couleur dédiée. """
    # attention, pas plus de 8 salles, sinon il faudrait des couleurs supplémentaires
    rc('text', usetex=True)
    nbr_o = nbr_optimal(liste_intervalles)
    ylim((-0.25, len(liste_intervalles) - 0.25))
    allocations = allocation(liste_intervalles)
    couleurs = list(zip(range(nbr_o), ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w']))
    t_min = min(map(lambda x: x[0], liste_intervalles))
    t_max = max(map(lambda x: x[1], liste_intervalles))
    for i, intervalle in enumerate(liste_intervalles):
        plot(intervalle, [i]*2, couleurs[allocations[i]][1])
        text(intervalle[0], i+0.10, "$C_" + str(i)+"$", {'fontsize': 20})
In [62]:
# Pour l'exemple 1
affiche_allocation(intervalles_1)
In [63]:
# pour l'exemple 2
affiche_allocation(intervalles_2)