# Orarul școlar al unei zile

    library(hours2lessons)

## Contextul de lucru

Asumăm existența unui set de date (data.frame) cu câte o linie pentru fiecare dintre lecțiile `prof|cls` ale unei zile de lucru, cu următoarele proprietăți principale (caracteristice unui orar școlar): fiecare profesor apare de cel mult 7 ori (și la o aceeași clasă, de cel mult două ori); fiecare clasă apare de cel puțin 4 ori și cel mult 7 ori (altfel spus, profesorii au câte cel mult 7 ore/zi; clasele au între 4 și 7 ore/zi). Ca exemplu este furnizat setul de lecții LSS.

În coloana `prof` avem coduri (în loc de “numele profesorilor”) de lungime 3 sau 6, semnificând disciplina principală a profesorului (abreviată pe două litere) și un număr de ordine între cei (cel mult 9) încadrați pe o aceeași disciplină principală; un cod de lungime 6, dacă există, reprezintă un cuplaj (sau “profesor fictiv”), alipind două coduri de lungime 3: cei doi profesori au de făcut lecția respectivă (într-o aceeași oră) cu câte o grupă de elevi ai clasei `cls`.

    str(LSS)  # un exemplu de set de lecții
    #> tibble [204 × 2] (S3: tbl_df/tbl/data.frame)
    #>  $ prof: chr [1:204] "Mt1" "Is3" "Ro9" "En4" ...
    #>  $ cls : chr [1:204] "05A" "05A" "05A" "05A" ...

    LSS %>% dplyr::filter(nchar(prof)==6)  # listează cuplajele existente
    #> # A tibble: 2 × 2
    #>   prof   cls
    #>   <chr>  <chr>
    #> 1 Ds1Mz1 12E
    #> 2 Fr1Gr1 12D

Admitem și existența unor tuplaje, într-un set suplimentar de date: profesorii din acest set au de intrat în câte o aceeași oră a zilei, la câte o grupă constituită din elevi de la două, trei sau chiar patru clase (sau eventual, la câte una dintre clasele respective); formal, un tuplaj este reprezentat inițial înscriind în `prof` vectorul profesorilor și în `cls` vectorul claselor “tuplate” acestora. Ca exemplu este furnizat setul de lecții tuplate Tuplaje.

    Tuplaje
    #>              prof         cls
    #> 1         Ds1 Mz1     10E 11E
    #> 2     Fr2 Fr3 Gr1 09A 09C 09D
    #> 3 Fr1 Fr2 Fr3 Gr2 10B 10C 10E
    #> 4     Fr1 Fr2 Gr1     11C 11E

De observat că dacă numărul de profesori este (cu `1`) mai mare ca al claselor, atunci… avem de înființat noi cuplaje (de exemplu, `Fr1Fr2` la clasele `10B` și `11C`, corespunzător tuplajelor din liniile 3 și 4).

Sarcina pachetului: avem de adăugat pe setul tuturor lecțiilor o coloană `ora`, astfel încât profesorii (inclusiv cei fictivi) să nu-și suprapună lecțiile (în câte o aceeași oră) și pe de altă parte, profesorii implicați într-un tuplaj să intre într-o aceeași oră, la clasele din tuplajul respectiv (fără suprapunere cu lecțiile vreunui alt tuplaj și nici cu lecțiile proprii ale membrilor tuplajului respectiv).

## Algoritmul de alocare pe ore a lecțiilor

Parafrazăm aici, algoritmul folosit în funcția `mount_hours``()` pentru a constitui un orar al lecțiilor.

Mai întâi, se investighează setul de tuplaje (dacă există) și dacă este cazul se înființează noi cuplaje (egalând în fiecare tuplaj numărul de clase cu cel de profesori); se completează setul principal de lecții (care conține lecțiile profesorilor și cuplajelor inițiale, din afara tuplajelor) cu lecțiile din setul suplimentar al celor tuplate (dacă există un profesor care cumulează mai mult de 7 ore, atunci… STOP).

Se împart lecțiile după clasă (constituind o listă care asociază fiecărei clase, setul lecțiilor acesteia).

Se parcurge lista claselor, într-o ordine aleatorie dar ponderată de coeficienții betweenness (în ordine crescătoare) rezultați printr-o funcție internă (în care utilizăm două funcții din pachetul `igraph`) pe graful claselor după numărul de profesori comuni la câte două clase.

Se înființeză un vector care asociază fiecărui profesor câte un octet (inițializat cu `0L`) în care urmează să se înregistreze alocările făcute până la momentul curent, lecțiilor profesorului respectiv (un bit `1` în poziția `i=0:6` arată că i s-a alocat deja ora `(i+1)`).

Se etichetează lecțiile clasei curente cu o permutare de ore (după numărul de ore/zi ale clasei); dacă astfel, la vreunul dintre profesorii clasei, se constată o suprapunere cu vreo lecție alocată anterior (la o clasă întâlnită înaintea celei curente), atunci se încearcă o altă permutare de ore (pentru lecțiile clasei curente). Dacă pentru niciuna dintre permutările încercate, nu se poate evita conflictul cu alocările anterioare, atunci… se abandonează parcurgerea mai departe a listei claselor, se reinițializează vectorul octeților de alocare asociați profesorilor și se reia parcurgerea listei claselor, dar într-o altă ordine (aleatoriu, cu “preferințe” date de coeficienții asociați claselor).

Dacă pentru clasa curentă se nimerește o etichetare cu ore convenabilă (fără suprapuneri cu octeții de alocare ai profesorilor clasei, constituiți până la momentul curent), atunci se înregistrează pe biți (în octeții de alocare ai profesorilor clasei) alocarea respectivă și se trece la următoarea clasă.

Corelațiile induse de normele de încadrare a profesorilor (care limitează numărul de ore pe profesor și pe clasă) asigură că la un moment dat (după un număr mai mare sau mai mic de reluări) se va nimeri totuși o ordine de parcurgere a claselor prin care lecțiile fiecărei clase sunt etichetate cu ore, fără conflicte; reunind în final orarele claselor, rezultă orarul `prof|cls|ora` (în format lung) pe ziua respectivă.

Fiind implicate elemente aleatorii, la fiecare nouă execuție a funcției `mount_hours()` se va obține de regulă câte un alt orar. Subliniem că nu se are în vedere, numărul de ferestre rezultate pe orarele individuale ale profesorilor (fiind sarcina unui alt pachet, să ajusteze orarul încât numărul total de ferestre să fie cât se poate de mic).

Funcția `long2matrix``()` transformă orarul din “formatul lung” într-o matrice în care fiecare linie indică pentru câte un profesor, clasele la care va intra acesta în fiecare oră (iar “`-`” marchează o fereastră sau o oră liberă, din afara orarului individual respectiv).

De exemplu, pentru seturile `LSS` și `Tuplaje` găsim orarul profesorilor angajați în cuplaje sau în tuplaje (cu sublinierea că la o nouă execuție, vom obține un alt orar, poate în mai puține încercări, poate mai bun sau poate, mai rău):

    mount_hours(LSS, Tuplaje) %>% as.data.frame() %>%
        dplyr::filter(grepl("Fr|Gr|Ds|Mz", .$prof)) %>%
        long2matrix() %>% as.data.frame()
    #>          1   2   3   4   5   6   7
    #> Ds1Mz1 12E   -   -   -   -   -   -
    #> Fr1Gr1   -   -   -   -   - 12D   -
    #> Gr2    10E 05A   -   -   -   -   -
    #> Fr1Fr2 10B   -   - 11C   -   -   -
    #> Fr3    10C 09C   -   -   -   -   -
    #> Gr1    07C 09D   - 11E 11E   -   -
    #> Fr1      - 06C 10B   -   -   -   -
    #> Ds1      -   -   - 06B 09E 10E 07A
    #> Fr2      - 09A 11C   - 10C   -   -
    #> Mz1      -   - 07D 05C 08B 11E   -

În ora în care profesorul fictiv `Ds1Mz1` intră la clasa `12E`, profesorii `Ds1` și `Mz1` nu au ore proprii (încât pot intra împreună, pe grupe, la `12E`); observăm la fel, pentru cuplajul `Fr1Gr1`.
Profesorii `Fr1`, `Fr2` și `Gr1` formau un tuplaj pe clasele `11C` și `11E` și observăm că s-a înființat profesorul fictiv `Fr1Fr2` care intră la `11C` într-o aceeași oră în care `Gr1` intră la `11E` (oră în care `Fr1` și `Fr2` sunt liberi).
`Fr1Fr2` intră încă într-un tuplaj, cu `Fr3` și `Gr2`, pe clasele `10B`, `10C` și `10E` și se vede pe coloanele orare de mai sus că lecțiile acestui tuplaj cad într-o aceeași oră și nu există suprapuneri de lecții ale membrilor tuplajului.
Iar lecțiile tuplajului (`Fr2 Fr3 Gr1 / 09A 09C 09D`) cad și ele într-o aceeași oră, fără suprapuneri cu alte lecții ale profesorilor respectivi.

Algoritmul de etichetare cu ore descris mai sus se poate adapta și pentru repartizarea echilibrată pe zilele de lucru, a lecțiilor `prof|cls` specificate în încadrarea săptămânală inițială, a profesorilor (în loc de permutări de ore, folosim permutări de zile); dar desigur, aceasta ar fi sarcina unui alt pachet…