momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

Noul orar (partea a cincea)

limbajul R | orar şcolar
2024 oct

Observație (… mai târziu, decât niciodată)

Am încheiat [1] cu o exemplificare de matrice-orar a unei zile, ordonând însă liniile după nume (alfabetic, facilitând examinarea vizuală a orarelor cuplajelor și membrilor acestora); dar divagând astfel spre aspectele vizuale, am scăpat din vedere să clarificăm aspectul într-adevăr important: ordinea inițială a liniilor din matricele orare (obținute prin mount_hours()) este dată de coeficienții betweenness pe ziua respectivă, ai profesorilor. Putem și proba direct, aceasta (transformând în data.frame și ținând cont că rownames(.) conține numele de linie):

hm <- ORR[["Vi"]] %>% hourly_matrix() %>%
      as.data.frame() %>% mutate(BTWprof = BTW_prof[["Vi"]][rownames(.)])
print(hm, quote = FALSE)



Graful profesorilor, pe "Vi"

1 2 3 4 5 6 7 BTWprof Mt6 10D - - - - - - 0.00010 Mt7 11B - - - - - - 0.00010 eS1 5A - - - - - - 0.00010 Mz1 - 10B - - - - - 0.00010 Ps1 11F 10C - - - - - 0.00094 En5En1 11A - - - - - - 0.00096 En4En3 - 9A - - - - - 0.00116 Gr1 - 5A - - - - - 0.00133 In3 - 9B - - 9B 9B - 0.00154 In3In2 10B - - - - - - 0.00242 TI1 10C 11F 10E - - - - 0.00409 Fl1 12C 12F - - - - - 0.00421 ... En3 - - 11C 6A 9A 11F - 0.03274 Ro3 9B 11A - - 12F 10A - 0.03330 Mt1 - 12B 11A 9A 10A 12A - 0.03434 Mt3 - - 10F 5A 8A 6A 7A 0.03446 Gg1 - - 12B 12F 11B 10D 8A 0.03891 Rl1 - - - 10D 12E 10C 9B 0.04693 In2 - - 8A 10B 9D 12B - 0.04792 Ro1 - 7A - 11D 6A 11B 10B 0.06315

Pe graful zilei "Vi" în care doi profesori sunt adiacenți dacă au măcar o clasă în comun, prin nodurile asociate primelor 4 nume nu trece nici o geodezică, iar prin nodurile Ro1, In2 și Rl1 trec cel mai multe geodezice (v. [1] și graful redat). Tabelul începe cu profesorii care au una-două ore (că „puține ore” înseamnă și ”puține clase” în comun cu ceilalți, deci valori BTWprof mici); tot pe la început sunt și cei care au 3 ore, dar la clase pentru care coeficienții BTW_cls[["Vi"]] sunt mici.
Clasele cu cei mai mari coeficienți (v. [1]):

> BTW_cls[["Vi"]][23:27]  # cei mai mari
        7A        11D        12B         6A         8A 
0.03193260 0.03235857 0.03481265 0.03610371 0.04774244 

apar firește, pe ultimele linii: In2 are 4 ore, între care și la 8A și 12B, iar din cele 5 ore ale lui Ro1, trei sunt la 7A, 11D și 6A; având ore la clase cu BTW_cls mare, coeficienții BTW_prof devin și ei, mari (simplificând: "importanța" profesorului e dată și de "importanța" claselor primite, pe lângă numărul de clase comune cu cei aflați deasupra).

Mai avem de subliniat această proprietate esențială: oricare clasă apare câte o singură dată pe fiecare coloană de rang cel mult egal cu numărul de ore/zi ale clasei și nu apare pe coloanele de rang mai mare ca acesta (iar de aici rezultă și că fiecare clasă își începe programul de la prima oră a zilei). Justificarea este evidentă: dacă o clasă ar apărea de două ori pe o coloană orară, atunci ar fi doi profesori în acea oră la clasa respectivă — situație care fusese interzisă de către mount_hours().

Reducerea numărului de ferestre din orarul zilei

Considerăm lista matricelor orare asociate orarelor (în „forma normală”) pe zile:

ORR <- readRDS("Orar1.RDS")  # orarul curent, pe zile (în forma normală)
mORR <- map(ORR, hourly_matrix)  # lista matricelor orare ale zilelor

În unele cazuri, ferestrele pot fi eliminate imediat:

> mORR[["Vi"]][c("Bl2", "Gr3"), ] %>% print(quote=FALSE)
        1   2  3  4 5  6  7
    Bl2 11C 9F -  - 7A -  -
    Gr3 -   -  7A - -  9A -

Listând Tw1[["Vi"]], constatăm că Bl2 și Gr3 nu fac parte din cuplaje; cele câte două ferestre ale lor se elimină mutând 7A între coloanele 5 și 3 (într-un sens și respectiv — pentru ca 7A să nu fie de două ori pe aceeași coloană — în sens contrar).

În general însă, pentru a elimina o fereastră sunt necesare mai multe mutări bilaterale de clase (între două coloane); iată un exemplu, dar dintre cele mai simple:

        1   2   3   4   5   6   7
    Rl2 9A  -   11E -   -   -   -
    Fz1 -   11E 12D 12C 11D 9E  -
    Sp2 12B 12D -   10F -   10E -

Am putea „repara” fereastra din ora 2 a lui Rl2, fie mutând 9A între coloanele 1 și 2, fie (cum sugerează liniile redate mai sus) mutând 11E între coloanele 3 și 2; când 11E ajunge în coloana 2, la Fz1 trebuie să schimbăm între ele 11E și 12D; odată ce 12D a ajuns în coloana 2, la Sp2 trebuie să mutăm 12D pe locul liber din coloana 3. Mutările efectuate corespund unui circuit între cele două coloane: "-"-->11E-->12D-->"-".

Mai general, secvența de mutări succesive care sunt necesare pentru a aduce o clasă din coloana h1, în coloana h2 (păstrând unicitatea pe coloană pentru clase) este dată de unul dintre circuitele existente în graful orientat ale cărui arce unesc o valoare din coloana h1 (clasă sau loc liber "-") cu aceea de pe aceeași linie, din coloana h2 (v. [3]).

Funcția move_cls(Mop, h1, h2, Cls) returnează matricea rezultată din matricea orar inițială Mop, prin mutarea de-a lungul circuitului de pe graful menționat, a clasei Cls din coloana h1, în coloana h2.
Dar dacă pe circuitul respectiv, se întâlnește o clasă care face parte dintr-un tuplaj, atunci move_cls() refuză mutarea indicată (returnând imediat NULL, în loc de matricea orară); se procedează astfel pentru a evita bifurcațiile induse de faptul că o asemenea clasă trebuie să apară într-o aceeași coloană cu cealaltă clasă a tuplajului (și lucrurile se complică și mai mult, dacă sunt trei sau mai multe clase în tuplaj). Concret, am regizat astfel (v. [2]): pe matricea orar inițială marcăm temporar clasele din tuplaje cu 'X'; dacă move_cls() întâlnește 'X' atunci returnează NULL.

În principiu, reducerea numărului de ferestre ar decurge astfel: pe matricea orar curentă Mop se aplică o mutare move_cls() alegând aleatoriu o clasă și două coloane care o conțin și se repetă până când matricea orar rezultată prin mutarea curentă are un număr de ferestre mai mic decât Mop (și urmează să repetăm reducerea, pentru matricea rezultată).

Mai precis, ne bazăm pe următorul rezultat (v. soluția problemei L445, Recreații matematice Nr. 2 / 2023, p.175): există exact 99 de șabloane binare pe 7 biți, care conțin între biți '1' măcar câte un bit '0' (altfel spus, pentru maximum 7 ore/zi există exact 99 de orare individuale cu ferestre).
Am constituit lista SBC.Rda, conținând drept chei toate șabloanele binare posibile, de orare individuale cu ferestre; pe fiecare dintre aceste chei, SBC asociază o listă de „mutări corectoare” — după acest model (două ore, cu fereastră în a doua oră a zilei):

SBC[["5"]] <- list(h1 = c(1,1,3), h2 = c(2,4,2))  # "5" este octetul '00000101'
    > str(SBC)
    List of 99
     $ 5  :List of 2  # mutări corectoare de fereastră în a 2-a oră
      ..$ h1: num [1:3] 1 1 3
      ..$ h2: num [1:3] 2 4 2

cu semnificația următoare: fereastra din ora a 2-a a unui profesor care are prima și a treia oră se poate „repara” fie mutându-i clasa din ora h1[1] (=1) în ora h2[1] (=2), fie mutând clasa din ora h1[2] (=1) în ora h2[2] (=4), fie mutând clasa h1[3] (=3) în ora h2[3] (=2). După mutare, profesorul va avea fie primele două ore, fie a 2-a și a 3-a, fie a 3-a și a 4-a oră (fără fereastră, în oricare caz).

Se identifică ferestrele din orarul zilei curente și folosind SBC, se constituie o „listă de mutări corectoare” pentru aceste ferestre; prin move_cls() se efectuează câte o mutare din lista mutărilor corectoare și în principiu, se înlocuiește orarul inițial cu cel rezultat după mutare, dacă acesta are mai puține ferestre.
Funcțiile necesare sunt conținute în programul "Gaps.R", pe care l-am copiat din [2] și apoi l-am completat cu funcția actual_tupl() din [1]; programul de reducere a ferestrelor se formulează acum astfel:

# xgaps.R
rm(list = ls())  # elimină obiectele din sesiunea precedentă
library(tidyverse)

ORR <- readRDS("Orar1.RDS")  # orarele zilelor, în formă normală
mORR <- map(ORR, hourly_matrix)  # matricele orar ale zilelor
source("Gaps.R")  # funcțiile necesare pentru reducerea ferestrelor (v. [2])

Globals("")  # setează o serie de variabile globale (v. [2])
PCH <- actual_tupl()  # orarele tuplajelor, pe fiecare zi 
W <- list()  # pregătește lista orarelor cu număr redus de ferestre
for(zi in Zile) {
    set_globals(zi)  # particularizează pe ziua curentă, variabilele globale
    OZ <- mORR[[zi]]  # matricea orară a zilei
    NG1 <- count_gaps(OZ) # numărul inițial de ferestre
    Good <- 6  # numărul sperat de ferestre
    PCh <- PCH[[zi]]  # orele tuplajelor, pe ziua curentă
    for(i in 1:nrow(PCh))  # maschează tuplajele ('X'), în matricea orară
        OZ[PCh$prof[i], PCh$ora[i]] <- "X"
    Cmb <- combn(ncol(OZ), 2)  # combinările de 7 (6 sau 5) luate câte două
    prnTime(paste0(" (", NG1, " ferestre)\n")) 
    orr <- search_better(OZ)  # declanșează procesul de reducere a ferestrelor
    for(i in 1:nrow(PCh))  # demaschează tuplajele
        orr[PCh$prof[i], PCh$ora[i]] <- PCh$cls[i]  
    W[[zi]] <- orr[order(rownames(orr)), ]  
    prnTime("\n") 
}

Pe parcursul execuției, din search_better() se afișează numărul de ferestre din matricea orar curentă; de exemplu:

21:32:38  (40 ferestre)  # numărul inițial de ferestre din mORR[["Jo"]]
37  36  35  33  31  29  28  27  26  25  23  20  15  14  13  12  11  10  9  8 
7  6  5  * 5  4  3  * 3  21:33:58

Pe mORR[["Jo"]], inițial erau 40 ferestre (cu tot cu cele ale membrilor cuplajelor, care fuseseră ignorate în mount_hours()); după câteva mutări de clase, a rezultat un orar cu 37 de ferestre, apoi unul câte unul cu 36, 35, 33, 31 etc. ferestre; epuizând numărul maxim de iterații prestabilit în search_better(), s-a ajuns la un orar cu 5 ferestre, care apoi pe încă un anumit număr de iterații a fost redus în cele din urmă (pe un timp de execuție aproape dublu, celui necesar în prima fază) la un orar cu numai 3 ferestre.

Execuția programului pe toate zilele (una după alta) durează în total cam 10 - 15 minute (pentru cazul orarului de față); repetând execuția într-un moment sau altul, rezultă orare care (datorită naturii aleatorii a mecanismului de reducere) diferă între ele pe o aceeași zi, inclusiv în ceea ce privește numărul de ferestre și distribuția acestora pe profesori.

Salvând orarele rezultate în mai multe execuții (unele numai pentru câte o zi, înlocuind for(zi in Zile) cu for(zi in Zile[2]) de exemplu)), și preluând apoi din fiecare, acel orar al zilei care are cel mai mic număr de ferestre:

> W1 <- readRDS("45536.RDS")  # 4 ferestre/Lu, 5 pe Ma, etc.
> W2 <- readRDS("44646.RDS")
> W3 <- readRDS("--4--.RDS")  # executat numai pentru ziua a 3-a
> W4 <- readRDS("-3---.RDS")
> Wend <- list(Lu = W1[["Lu"]], Ma = W4[["Ma"]], Mi = W3[["Mi"]], 
               Jo = W1[["Jo"]], Vi = W1[["Vi"]])
> saveRDS(Wend, "orar_end.RDS")
> sapply(Zile, function(zi) { set_globals(zi); count_gaps(Wend[[zi]]) })
    Lu Ma Mi Jo Vi 
     4  3  4  3  6  # în total, 20 ferestre

ne-a rezultat orarul final "orar_end.RDS", care are în total numai 20 de ferestre — reprezentând 2.4 % din totalul 835 al lecțiilor.

Dacă vrem, putem înscrie orarele respective într-un fișier-text:

sink("orar_end.txt")
    for(zi in Zile) {
        print(zi)
        print(PCH[[zi]])  # orarul tuplajelor
        print(Wend[[zi]], quote=FALSE)
        cat("\n")
    }
sink()

Redăm aici orarul pe Vi (pe care n-am reușit reducerea la mai puțin de 6 ferestre):

[1] "Vi"
   prof cls ora
9   Ds1 10A   2  # în săptămâna următoare: 10B (tot în ora 2)
10  Mz1 10B   2  # în săptămâna următoare: 10A
         1   2   3   4   5   6   7                  1    2    3    4    5    6    7
Bl1    11A 12D  9A 10E   -   -   -           In2    -    -  10B    -   9D  12B   8A
Bl2      -   -   -   -  9F 11C  7A           In3    -    -    -    -   9B   9B   9B
Bl3    10B 11D   -   -   -   -   -        In3In2    -    -    -  10B    -    -    -
Ch1    10E 11C   - 12B  8A 10C   -           In4  11E   9F   9E  11C  12C    -    -
Ch2    12D 10D 11D 10A   -   -   -           Is1    -    -   5A   9E   6A  10F    -
Ds1     6A 10A 12F   -   -   -   -           Is2  12F   8A   7A    -    -    -    -
Ec1      -   - 11E 11F 10E  8A   -           Lg1   9D  12A    -    -    -    -    -
En1      -   -   - 12F 10C  9D   -           Mt1    -  11A  12B   9A  12A  10A    -
En2      - 12B   -   - 12D 11B   -           Mt2   9F   9E  10C    -  11F  12C    -
En3     9A   - 11F   - 11C  6A   -           Mt3   8A   5A  10F   6A    -   7A    -
En4En2 10A   - 12A   -   -   -   -           Mt4    -    -  11C  12E  11E  11D    -
En4En3   -  9A   -   -   -   -   -           Mt5   9B   9D    -    -    -    -    -
En5      -   -   -  9F 10F 11A   -           Mt6  10D    -    -    -    -    -    -
En5En1   -   - 11A   -   -   -   -           Mt7  11B    -    -    -    -    -    -
eS1     5A   -   -   -   -   -   -           Mz1    -  10B    -    -    -    -    -
Fl1    12C 12F   -   -   -   -   -           Ps1  10C  11F    -    -    -    -    -
Fr1      -   -   - 11E 12E  9E  9E           Rl1  12E   9B  10D  10C    -    -    -
Fz1    11D 11E 12C 12D  9E   -   -           Rl2    -    -    -    -   9A  11E    -
Fz2    12A  6A 11B  7A   -   -   -           Ro1    -   7A   6A  11D  11B  10B    -
Fz3    10F 10E 10A   -   -   -   -           Ro2   9E  10F   9F    -    -    -    -
Fz4      -   -   -   -   - 12E 10B           Ro3    -    -   9B  11A  10A  12F    -
Gg1    12B 11B   -  8A 12F 10D   -           Ro4  11C  12E   8A  12C    -    -    -
Gg2      -   -   -  9D  5A 11F   -           Ro5    -    -    -  12A  10D  12D    -
Gr1      -   -   -   -   -  5A   -           Sp1   7A  12C  12E   5A    -    -    -
Gr2Gr1   -   -  9D 10D 11D   -   -           Sp2    -    -  12D  10F  12B  10E    -
Gr3      -   -   -   -  7A  9A   -           Sp3    -    -    -   9B  10B   9F    -
In1      -   -   - 11B 11A 12A   -           TI1  11F  10C  10E    -    -    -    -

Am marcat cu roșu, cele 6 ferestre; subliniem că ferestrele nemarcate sunt false: En2, En3 și In2 au câte o fereastră falsă, sub acoperirea cuplajele En4En2, En4En3 și respectiv In3In2. Mutarea clasei 9A din coloana 1 în coloana 4 ar acoperi fereastra lui En3, dar se vede imediat că ar face o fereastră lui Mt1; mutarea clasei 10A din prima în a 4-a coloană, ar acoperi fereastra lui En2 dar ar conduce la apariția unei ferestre la Ro1.

Precizăm că în fiecare zi, ferestrele care apar (4, 3, 4, 3 respectiv 6) sunt de câte o singură oră, la un profesor sau altul.

Concluzii…

Am plecat (v. [4]-I) de la un orar școlar preluat de pe site-ul unei anumite școli (realizat prin aplicația furnizată de ascTimetables și adaptată la noi de Siveco). Orarul respectiv era prezentat printr-un fișier în format PDF, conținând imagini ale unor pagini scanate (fiecare pagină pozá orarul câte uneia dintre clasele școlii); dificultatea majoră pentru extragerea datelor din imaginile respective constă în faptul că numele de profesori și discipline școlare apar ca fiind scrise "de mână" (și nu folosind un font obișnuit) — și tocmai aceasta ne-a provocat să ne ocupăm de orarul respectiv.

În [4] (părțile I:VIII), am investigat recunoașterea și extragerea textului de pe imaginile respective, folosind în principal programul Tesseract și anumite programe din ImageMagick (display, mogrify și altele); folosind apoi $\mathbf{R}$, am organizat textul extras de pe imagini, într-un set de date coerent — conținând toate lecțiile prof|cls|obj|zi|ora desfășurate conform orarelor respective.

Am putut constata astfel, că orarul propus pe site-ul școlii (și semnat de ascTimetables) este neprincipial; repartizarea pe zile a lecțiilor prof|cls este neuniformă, cam din toate punctele de vedere: într-o zi sunt în total 157 de ore, într-o alta sunt 176 de ore; o clasă are o zi cu 5 ore și o zi cu 7 sau chiar 8 ore; un profesor are într-o zi 2 ore și în alta are 7 ore; cele 5 ore de "Engleză" la o clasă se fac în două zile și multe obiecte se fac pe câte două ore pe zi la clasă; unele clase își încep programul de la a doua oră a zilei.
Probabil că ideea principală a fost aceea (uzuală) de a mulțumi profesorii, în privința zilelor alocate (și dacă ai 20 de ore, tot ai una-două zile libere) și probabil, în privința ferestrelor (într-adevăr, orarul pe Vi conține doar două ferestre)…

Principiul recunoscut ca fiind cel mai favorabil pentru "procesul de învățare", constă în distribuirea lecțiilor în mod echilibrat pe zile, clase și profesori; la fiecare clasă, lecțiile pe un același obiect trebuie repartizate câte una pe zi (și nicidecum, înghesuite în una-două zile); toate clasele își încep programul zilnic la o aceeași oră a zilei; lecțiile fiecărui profesor trebuie distribuite uniform, de-a lungul săptămânii.
Desigur… profesorii trebuie preveniți din start, cinstit și ferm, asupra principiilor de construcție a orarului (te pot încadra la cutare clasă pe 5 ore la cutare obiect, dar numai dacă ești de acord să vii pe câte o oră în fiecare zi și nu-ți pot garanta că ora respectivă va fi la începutul, la mijlocul sau la sfârșitul zilei).

Plecând de la datele de încadrare prof|cls extrase din orarul original, în [1] (părțile I:V) am constituit un nou orar, echilibrat: sunt 167 sau 168 de ore/zi; lecțiile fiecărei clase și respectiv, lecțiile fiecărui profesor, sunt distribuite omogen pe zile; lecțiile fiecărei discipline școlare sunt distribuite uniform de-a lungul săptămânii, la fiecare clasă; toate clasele își încep programul de la prima oră a zilei.
În plus, numărul total de ferestre este cât se poate de mic (20 ferestre, în timp ce pe orarul original era și o zi cu deja 16 ferestre) și fiecare „fereastră” măsoară câte o singură oră (nu avem profesor care să aibă două ferestre într-o aceeași zi).

Probabil s-ar putea obține un asemenea orar echilibrat și folosind ascTimetables… N-ai avea decât să introduci datele necesare în formularele oferite pe ecran (doar că ar trebui să le specifici "principial", ceva mai inteligent decât cum se obișnuiește) și să aștepți orarul semnat de ascTimetables; însă pentru noi, o asemenea experiență este de nedorit (nu utilizăm Microsoft-Windows, nici -Office și implicit, nu agreem "point-and-click" și nici formularele. Nici dacă s-ar plăti, pentru asta!).

Pentru realizarea acestui orar echilibrat am folosit programele $\mathbf{R}$ expuse în [2] și [3], dar ne-am străduit (și de această dată) să explicităm și să clarificăm logica lucrurilor.

O lecție oarecare îmbină trei entități: un profesor (sau un cuplu de profesori), o disciplină școlară și o clasă; o aceeași lecție prof|obj|cls se repetă în cursul săptămânii de un număr de ori care s-a fixat în prealabil (prin "planul de încadrare" a profesorilor în școală). Un orar alocă lecțiile pe zile și pe orele zilei, astfel încât oricare două lecții (distincte sau nu) să fie alocate pe clasă în intervale de timp zi|ora distincte.

Întâi simplificăm datele: abreviem disciplinele prin câte două litere și denumim profesorii după disciplina pe care au cel mai multe ore și după numărul de ore; de exemplu, cei doi profesori care au "Biologie" ca disciplină principală pot fi referiți prin Bl1 și Bl2. Un caz special îl constituie lecțiile care se desfășoară "pe grupe": într-un același interval orar, prima grupă de elevi din clasa 9B să zicem, face de exemplu "Engleză" (-"avansați") cu profesorul En2, iar a doua grupă face "Engleză" (-"începători") cu profesorul En4; deci lecțiile En2|9B și En4|9B trebuie să se suprapună într-un același interval orar zi|ora și totodată, trebuie să nu se suprapună cu vreo altă lecție a lui En2 sau En4 — condiții care vor fi mai ușor de gestionat dacă înființăm un "profesor fictiv" En2En4, pentru lecțiile "pe grupe" ale celor doi profesori (de exemplu En2En4|9B|Mi|3 ar desemna lecția din ziua Mi ora 3, efectuată pe câte o grupă a clasei 9B de către cei doi profesori). Inventând profesori fictivi pentru cuplaje, lucrurile revin la regula generală: oricare două lecții cu același prof sau cls (inclusiv de exemplu, En2En4|9B, En2|11A, En4|5A) trebuie să nu se suprapună în timp.
Să observăm că denumind profesorii după disciplină, ne putem lipsi de câmpul obj. Desigur, unii profesori au în încadrare și discipline "secundare" (de exemplu, Bl2 are poate și "Consiliere" la o anumită clasă). Pentru a ține cont de toate cele, constituim dicționare pentru discipline și pentru profesori și deasemenea, un set de înregistrări prof|Clase|Obj_sec pentru discipline secundare — astfel ca în final, să putem reconstitui numele și denumirile reale.

În vederea modelării cât mai simple și cât mai eficiente (până la urmă, "pe biți") a relațiilor de alocare între lecții, am înființat două dicționare care specifică pentru fiecare cuplaj sau membru al unui cuplaj, profesorii (fictivi sau nu) de care depinde alocarea pe zile sau ore a lecțiilor sale. Dacă este cazul (și de obicei este), pentru tuplaje sau eventual pentru câțiva anumiți profesori, înființăm un "orar parțial" și eliminăm deocamdată, lecțiile respective.

Dacă vrem să respectăm echilibrele menționate mai sus, atunci trebuie să constituim întâi o repartiție pe zile a lecțiilor; pentru aceasta am folosit programul by_days.R din [2], a cărui idee de bază constă în etichetarea cu o permutare de zile, repetată de sus în jos pe tabelul lecțiilor fiecărei clase, într-o anumită aranjare a liniilor respective. Am folosit deasemenea, un set de funcții care permit intervenții interactive asupra repartizării pe zile obținute (în scopul unei depline omogenizări).

Având o repartizare pe zile echilibrată a lecțiilor, trecem la repartizarea pe orele 1:7 a lecțiilor fiecărei zile; ne-am bazat pe funcția mount_hours() din [2], a cărei idee de bază constă în a folosi un set de "șabloane binare" pentru a urmări alocările de ore pe lecțiile din ziua respectivă ale fiecărei clase, parcurgând clasele într-o ordine aleatorie dar apropiată de ordinea crescătoare a coeficienților betweenness.

Etapa finală constă în reducerea numărului de ferestre din orarele zilelor, măcar până pe la 5% din totalul lecțiilor (și a fost subiectul părții "a cincea", de față).

Nu ne mai ocupăm aici de finalizarea lucrurilor: ar trebui să recuperăm din dicționarele pe care le-am constituit, numele reale de profesori și discipline și să revenim la disciplinele secundare, unde este cazul; ar mai trebui apoi, să formulăm într-un format potrivit, orarele respective (pe fiecare zi în parte, pe clase, pe discipline, etc.).

Ar mai fi de observat că ingredientul esențial este limbajul R; R "is a free software environment for statistical computing and graphics", iar noi l-am folosit pe aici în cu totul alt scop decât cele obișnuite în statistică…
Dar nu ne-am referit sistematic, la $\mathbf{R}$ (și nici la "dialectul" tidyverse) — cam din două motive: pe de o parte, numele de funcții sunt deja foarte sugestive ("filter", "select", "arrange", "mutate", "abbreviate", "plot" etc.); pe de altă parte, dacă ai instalat $\mathbf{R}$, dispui deja prin consola asociată, de help() pentru toate cele (precum și de o serie de manuale). Ce să adăugăm noi ? Poate vreo observație "conceptuală", ici si colo — de exemplu, pentru factor al unui set de date, am observat undeva mai sus, legătura cu binecunoscutul procedeu prin care construim inelul claselor de resturi… Altfel este de presupus că se știe câte ceva despre "date" și despre "forma normală" a datelor (de pe la capitolul "Baze de date"), ca și despre "vectori", "liste", "matrici", etc.

vezi Cărţile mele (de programare)

docerpro | Prev | Next