Orare școlare echilibrate

library(days2lessons)

De unde plecăm…

Pentru anul școlar care ar sta să înceapă, directorul (sau managerul) școlii întocmește un plan de încadrare, stabilind între altele (în conformitate cu anumite normative legale) ce discipline școlare sunt de parcurs la fiecare clasă și care profesori, la care clase și pe câte ore de curs, vor face lecțiile respective.
Folosind datele desprinse din planul de încadrare, urmează să se formuleze un orar care să indice pe zile și ore ale zilei, la care clasă are de intrat fiecare profesor; firește, un profesor nu poate intra, într-o zi și oră date, decât la o singură clasă; iar o clasă nu poate avea într-o zi și oră date, decât un singur profesor — exceptând cazul când, pentru unele discipline, s-a decis despărțirea clasei în două grupe de elevi, repartizate fiecare, pentru ziua și ora respectivă, la câte un profesor.

Planul de încadrare este bazat pe bugetul de salarii alocat școlii; după volumul acestuia, se stabilește câte “norme didactice” pot fi constituite, deci câți profesori pot fi angajați, pentru totalul claselor și elevilor. În schimb, orarul școlar are de reflectat nu interese, ci anumite “principii pedagogice” specifice activității de învățare în care urmează să fie antrenați elevii, cu profesorii respectivi; se recomandă ca elevii să aibă zilnic 5, 6, excepțional 7 ore; profesorii să nu fie încărcați cu mai mult de 6, excepțional 7 ore/zi; lecțiile pe o aceeași disciplină, la o clasă sau alta, să fie distribuite echilibrat de-a lungul săptămânii, de regulă câte una pe zi (și nicidecum, înghesuite în două zile).
Desigur, aceste principii trebuie amintite din start —evitând contestări ulterioare, bazate și ele pe diverse “principii”— și chiar cu o anumită fermitate din partea angajatorului (“te pot angaja pe 3 ore de ‘Fizică’, rămase neacoperite la clasa cutare, dar numai dacă accepți să le faci câte una pe zi”…); dincolo de principii, pot să fie luate în seamă și unele interese personale (dacă nu țin de “nazuri”), dar important ar mai fi, pentru un orar acceptabil, ca numărul total de ferestre apărute în orar să fie pe cât se poate de mic.

Logica lucrurilor

Constituirea unui orar echilibrat pleacă de la două sau trei observații simple… Din planul de încadrare se deduce pentru fiecare profesor și clasă, numărul de ore pe săptămână, pentru lecțiile pe disciplina respectivă; numai că în orar trebuie evidențiată (pe câte o zi și oră) fiecare dintre lecțiile respective. Prin urmare, firesc este ca în loc de câte o singură linie Disciplină / Profesor / Clasă / Număr_ore, să înscriem câte un număr de Număr_ore linii identice (păstrând pe fiecare numai primele trei câmpuri).
Această lungire a tabelului de încadrare asigură imediat o posibilitate foarte simplă pentru a repartiza echilibrat pe zile, lecțiile unei clase: pe lista care enumeră pe rând lecțiile fiecărui profesor al clasei se adaugă o coloană pe care se repetă de sus în jos (pe cât încape) secvența zilelor; astfel, lecțiile fiecărui profesor, fiind pe linii consecutive, vor fi alocate în zile diferite (deci în principiu, câte una pe zi).

Apoi, de ce să ne intereseze pe parcursul construcției orarului, numele și prenumele? N-are de ce, cu atât mai mult că dacă vom publica orarul cu “Nume și Prenume”, putem avea de-a face cu “legea drepturilor personale”… Putem abrevia denumirile disciplinelor pe câte două litere, desemnând apoi profesorii alipind celor două litere asociate disciplinei principale pe care este încadrat fiecare, un număr de ordine în lista celor de pe o aceeași disciplină (… “principală”, mascând temporar diversele discipline “secundare” care ar fi anexate unora dintre profesori — precum “Dirigenție”, sau “Cultură civică”, etc.). De exemplu, “Bi1”, “Bi2”, “Bi3” ar desemna cei trei profesori de “Biologie”.
Avantajul acestei convenții prealabile de notație constă în faptul că putem omite mai departe, câmpul pe care am nota disciplina corespunzătoare fiecărei lecții (ea deducându-se din codul de 3 caractere al profesorului; desigur, la capăt vom putea avea în vedere și disciplinele secundare, dacă le înregistrăm în prealabil într-un tabel, pe profesori și clase); pe de altă parte, devine ușor de înregistrat cuplajele (și ușor de corelat apoi, la repartizarea lecțiilor, cu membrii respectivi): dacă Gr1 și Gr2 au de făcut împreună o lecție “pe grupe” ale unei clase, atunci înființăm “profesorul fictivGr1Gr2, atribuidu-i în orar clasa respectivă.

În sfârșit, să observăm că, măcar până a deveni o “problemă” (clasică sau altfel), constituirea orarului școlar are de-a face doar cu seturi de date; este dat setul (sau tabelul) tuturor lecțiilor prof|cls ale săptămânii și acesta trebuie extins cu un câmp pe care să figureze ziua și cu unul în care să înscriem ora, pentru fiecare lecție (poate fi dat și un mic set suplimentar, cu lecții care din diverse motive, trebuie plasate numai în anumite zile). Și se știe deja că pentru lucrul eficient cu seturi de date (și nu numai pentru treburile statisticii), cel mai potrivit este să folosim limbajul R (cu “dialectul” tidyverse)…

Este normal să nu amestecăm lucrurile: întâi vom repartiza lecțiile pe zile (dat fiind că vrem ca repartizarea lecțiilor de-a lungul săptămânii să fie pe cât se poate de omogenă, pe zile, pe clase și pe profesori) și abia apoi, pentru fiecare zi în parte, vom repartiza lecțiile acesteia pe orele 1:7 ale zilei (ignorăm cazul în care școala funcționează cu două schimburi: dacă încadrarea profesorilor este judicios concepută, orarul unei asemenea școli revine la două orare pe orele 1:7, câte unul pentru fiecare schimb). Iar în final, după ce vom fi completat corect câmpurile de zi și oră, vom ajusta orarul rezultat astfel încât să reducem numărul total de ferestre la o valoare acceptabilă.

În pachetele hours2lessons și refitgaps ne-am ocupat deja de repartizarea pe orele 1:7 a lecțiilor dintr-o aceeași zi, respectiv de reducerea ferestrelor din orarul unei zile; în pachetul de față vizăm repartizarea echilibrată a lecțiilor prof|cls pe zilele săptămânii.

Cum procedăm…

Sunt de avut în vedere mai multe categorii de profesori (și de lecții). Majoritatea profesorilor au numai ore proprii (fiecare intră singur, la una sau alta dintre clasele pe care a fost încadrat); fie LS1 subsetul lecțiilor acestora. Unii profesori, pe anumite discipline, au ore proprii, dar intră și în cuplaje (în ora respectivă, clasa este împărțită ad-hoc în două grupe, care fac lecția independent una de alta, cu câte unul dintre membrii cuplajului respectiv); fie LS2 subsetul lecțiilor acestora. Pot exista și cuplaje externe, în care niciunul dintre membri nu are ore proprii (deci nu figurează în LS1); convenim să adăugăm cuplajele externe în LS1.

În sfârșit, pe anumite discipline există lecții la unele clase care trebuie să se desfășoare în câte o aceeași zi și oră, împerechind câte un profesor cu câte o clasă; le numim lecții “tuplate” și desemnăm prin “tuplaj” grupul respectiv de profesori și clase. Ne așteptăm ca tuplajele, dacă este cazul, să fie specificate într-un set suplimentar de date, să-l notăm LS3.

LS1, LS2 și LS3 sunt firește, disjuncte între ele. La un moment viitor vom lua în considerație și existența unui set de lecții prealocate pe zile (pentru cazul celor care, fiind încadrați în două școli, nu pot fi prezenți decât în anumite două-trei zile)…

Cel mai simplu de alocat pe zile sunt lecțiile din LS2 și LS3: ordonăm după profesor lecțiile subsetului respectiv și adăugăm o coloană pe care repetăm o aceeași permutare de zile; această simplitate ne va permite să generăm dinamic, repartițiile respective, după ce vom fi obținut o repartiție pe zile pentru lecțiile din subsetul LS1: dacă prin cumularea celor trei repartiții, găsim clase pentru care numărul de ore/zi este în afara intervalului 5:7, sau profesori care cumulează mai mult de 7 ore/zi, atunci regenerăm repartițiile pe zile pentru LS2 și LS3 (schimbând permutarea de zile anterioară)… Putem spera că la un moment dat nimerim o repartizare pe zile convenabilă, fiindcă pe de o parte, cuplajele și tuplajele sunt relativ puține față de totalul lecțiilor și pe de altă parte… repartiția lecțiilor din LS1 este deja omogenă pe clase și cvasi-omogenă pe profesorii respectivi.

Repartizarea pe zile, echilibrată, a lecțiilor din LS1 este mai anevoioasă, necesitând de obicei mai multe reluări. Pentru fiecare clasă, într-o anumită ordine a acestora, se montează coloana zilelor alocate lecţiilor ei: se dispun lecțiile clasei după profesori și se caută o permutare de zile a cărei aplicare consecutivă pe lecțiile respective asigură că până la momentul curent, distribuțiile pe zile ale lecțiilor profesorilor clasei (cumulând cu cele rezultate pentru ei până la clasa curentă) sunt cvasi-omogene (cu diferență de cel mult două ore, între zile). În caz de succes, se actualizează valorile în vectorul care păstrează numărul de ore alocate pe zile fiecărui profesor până la momentul curent și se trece la următoarea clasă, până ce se parcurg toate clasele. În caz de insucces (când pentru clasa curentă nu găsim nicio permutare de zile convenabilă)… stopăm parcurgerea listei claselor, re-inițializăm vectorul alocărilor pe zile și o luăm de la capăt, dar într-o altă ordine a claselor.
Normativele existente limitează suficient de mult numărul maxim de ore pe profesor și pe clasă, încât este de așteptat ca după un număr mai mic sau mai mare de reluări, să nimerim o ordine de parcurgere integrală a listei claselor, reușind astfel să montăm pe zile, lecțiile fiecăreia.

Un exemplu de lucru

Am înglobat setul de lecții LSS și setul asociat de tuplaje TPL:

str(LSS)
#> 'data.frame':    870 obs. of  2 variables:
#>  $ prof: chr  "Bl2" "Mt1" "Gr4" "In2" ...
#>  $ cls : chr  "5A" "5A" "5A" "5A" ...
str(TPL)
#> 'data.frame':    27 obs. of  2 variables:
#>  $ prof: chr  "Gr3 Gr4" "Gr3 Gr1 Gr4" "Gr3 Gr1 Gr4" "Gr3 Gr1 Gr4" ...
#>  $ cls : chr  "10E 10F" "11A 11B 11D" "11A 11B 11D" "12A 12B 12D" ...

În LSS sunt înregistrate și câteva cuplaje; de exemplu In2In3 și In2In1 vor afecta numărul de ore/zi alocate lecțiilor proprii ale lui In2, In3 și In1.
Din TPL deducem de exemplu că lecțiile Gr3 10E și Gr4 10F vor trebui să figureze în câte o aceeași zi.

Următoarea secvență produce într-un anumit timp, câte o repartizare echilibrată pe zile, a lecțiilor (și afișează distribuția pe zile a numărului total de lecții):

prnTime <- function(S = "")  # va afișa timpul curent
    cat(strftime(Sys.time(), format="%H:%M:%S"), S)
for(i in 1:2) {
    prnTime(" ")
    R123 <- mount_days(LSS, TPL)  # Dfh = 2 (valoarea implicită)
    prnTime("\n")
    addmargins(table(R123[c('cls','zl')]))["Sum", 1:5] %>% 
        as.vector() %>% print()
}
09:27:24  09:27:40       # durată: sub 20 secunde
[1] 186 188 181 188 185
09:27:40  09:28:13       # durată: sub 40 secunde
[1] 184 187 185 185 187

Dintre cele două distribuții rezultate ar fi preferabilă a doua (în care diferența între ore/zi este cel mult 3, nu 7 ca în prima).
Fiindcă am apelat mount_days() cu parametrul implicit Dfh = 2, în repartițiile pe zile rezultate fiecare clasă are o distribuție cvasi-omogenă a numărului de ore/zi (diferind de la o zi la alta cu cel mult 2 ore); desigur, cu Dfh = 3 obțineam mai rapid o repartiție, dar cu clase la care diferența între zile ajunge totuși, la 3 ore…

Desigur, repartiția pe zile rezultată poate fi “cizelată” interactiv, pentru a transforma distribuțiile cvasi-omogene în distribuții omogene; dar constituirea unui set de funcții care să afișeze distribuțiile pe zile existente la unele clase sau la unii profesori și să permită schimbarea zilei alocate unei lecții în scopul omogenizării unor distribuții individuale — ar fi probabil sarcina unui alt pachet (viitor).