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

Asupra unui orar școlar (IV)

R
2026 aug

În [1] (partea a II-a) am obținut (prin pachetele days2lessons și recastlessons) o repartizare pe zile a lecțiilor prof|cls care este perfectă, în sensul că în fiecare zi avem câte $\boldsymbol{\left\lfloor\frac{N}{5}\right\rfloor} + \left/0\;sau\,1\right/$ ore, în toate cazurile de luat în seamă pentru $N$: numărul tuturor lecțiilor săptămânii, sau numărul de lecții ale unei clase, sau numărul de ore (dacă este mai mare ca 9) pe care este încadrat profesorul, sau numărul de lecții pe o aceeași disciplină la o clasă.

Însă după ce am constituit (prin pachetul hours2lessons) un orar pentru această repartizare "perfectă" pe zile, am constatat că (prin pachetul refitgaps) nu prea putem reduce numărul de ferestre apărute în matricele-orar zilnice, mai jos de (17 12 12 15 12) — în total 68 de ferestre, adică 5.80% din totalul 1172 de lecții.

Pe orarul original (v. partea I) erau mai puține, numai 61 de ferestre, cu distribuția zilnică (7 13 11 14 16) — însă pe orarul respectiv, repartizarea lecțiilor pe zile arată foarte rău, fiind dezechilibrată în toate privințele: sunt profesori cu 0/1/2 ore într-o zi și 6 ore într-o alta; clase cu 5 ore într-o zi și cu 7 ore în alta; la multe clase, lecțiile pe discipline cu 3 sau 4 ore/săptămână se expediază în două zile (iar numărul de ferestre din orarul original putea fi încă redus sub 61; de exemplu, pentru lecțiile repartizate în ziua "Luni", în [1] avem prin refitgaps, un orar cu numai 4 ferestre în loc de cele 7).

În ce ne privește, adevărul este că am vrut prea mult — nu ne-am mulțumit cu rezultatul produs în câteva minute de days2lessons, ci am vrut ca repartizarea lecțiilor prof|cls pe zilele de lucru să fie perfectă, iar orarele asociate apoi pe fiecare zi să aibă cel mult vreo 4% ferestre (față de totalul lecțiilor)… În unele cazuri anterioare aproape că ne-a reușit; în altele, precum în cazul de față, nu prea ne reușește.

Vom proba aici că dacă renunțăm să mai perfecționăm interactiv (prin pachetul recastlessons) repartizarea pe zile produsă prin days2lessons, atunci putem obține orare zilnice cu puține ferestre (vreo 4% din totalul lecțiilor zilei).
În fond, days2lessons::mount_days() produce o repartizare pe zile deja acceptabilă: distribuția numărului de ore/zi este uniformă pentru fiecare clasă, iar pentru profesori este cvasi-uniformă (diferă de la o zi la alta cu cel mult două ore, pentru fiecare profesor); totodată, la fiecare clasă lecțiile cu un același profesor sunt distribuite uniform pe zile. În realitate, abordând pragmatic lucrurile, n-ar fi cazul să ne mai străduim pentru a transforma repartizarea pe zile rezultată astfel, într-una "perfectă" — ne putem mulțumi foarte bine, cu una "cvasi-perfectă"; o distribuție ca (5 5 5 4 5) este dintre cele omogene — cum am preferat noi, străduindu-ne interactiv pentru a asigura caracterul de "perfect" al repartizării pe zile a lecțiilor — dar este acceptabilă desigur și una "cvasi-omogenă", ca (5 4 6 4 5).

Recuperăm setul celor 1172 lecții prof|cls (în care transformăm din "factor" în "character" câmpul prof, având în vedere cerințele funcției mount_hours() pe care o vom angaja mai departe) și obținem o repartizare pe zile prof|cls|zl, "cvasi-perfectă":

library(tidyverse)
library(days2lessons)
LS <- readRDS("lessons.RDS") %>% mutate(prof = as.character(prof))
DZ <- mount_days(LS, Dfh = 1)  # saveRDS(DZ, "lss_days.RDS")

Am preferat să folosim Dfh =1, pentru a asigura distribuții omogene ale numărului de ore/zi la fiecare clasă (altfel, valoarea implicită 2 permite și distribuții cvasi-omogene pe unele clase, cu avantajul scurtării sensibile a duratei). Prin table() putem verifica faptul că distribuțiile sunt omogene pe fiecare clasă și sunt cvasi-omogene pe fiecare profesor; folosind și addmargins(), putem vedea dacă și distribuția pe zile a celor 1172 lecții este uniformă — de obicei nu este, dar repetând de câteva ori programul redat mai sus, am găsit și una uniformă: Sum 234 235 234 235 234 1172 și pe aceasta o avem în vedere mai departe.

Separăm pe zile setul tuturor lecțiilor (valorile $zl devin nume ale listei seturilor de lecții pe câte o aceeazi zi) și pe fiecare set de lecții dintr-o aceeași zi aplicăm mount_hours(), obținând câte un orar pentru fiecare zi (adică o repartizare a lecțiilor zilei pe orele 1:7, cu proprietatea că oricare două lecții sunt alocate în ore distincte):

lDZ <- lapply(split(DZ, ~ zl), function(D) D[, 1:2])  # saveRDS()
library(hours2lessons)
W <- lapply(lDZ, mount_hours)  # saveRDS()

Mai sus, mount_days() poate dura și câteva minute (dar nu zeci), mai ales dacă repetăm pentru a avea o distribuție mai bună a totalului; în schimb, mount_hours() are o durată totală de doar câteva secunde — fiindcă nu are pretenții față de numărul de ferestre.

Pentru a reduce numărul de ferestre, întâi transformăm orarele zilnice din formatul "lung" prof|cls|ora, în formatul de "matrice-orar" (prin funcția long2matrix()); apoi, pe matricele-orar respective aplicăm recast():

Wmo <- lapply(W, long2matrix)  # listă a matricelor-orar inițiale
library(refitgaps)
show_time(" ")  # afișează timpul curent
    WW <- lapply(Wmo, recast)
show_time("\n")
    # 17:34:50  17:38:42  ## (timp de lucru: sub 4 minute)
> sapply(WW, function(W) W[[2]])
    Lu Ma Mi Jo Vi
     9  6  9  9 10   # (numărul de ferestre, pe noile orare zilnice)

Prin cele trei secvențe de program redate mai sus, plecând de la încadrarea profesorilor sub formă de lecții prof|cls (în număr de 1172 lecții), am obținut mai întâi o repartizare suficient de echilibrată ("cvasi-perfectă") a lecțiilor pe zile și apoi orare zilnice cu un total rezonabil de ferestre (43 ferestre, adică 3.67% din totalul 1172 de lecții), cu o durată totală de execuție mai mică decât 10 minute.

Redăm aici (ordonând alfabetic) orarul rezultat pe Ma, zi pe care avem cel mai puține ferestre (marcate prin "-"; sunt două consecutive la p36 și câte una la alți 4 profesori):

> Ma <- WW[["Ma"]][[1]]  # matricea-orar cu 6 ferestre, pentru ziua Ma
> print.table(Ma[order(rownames(Ma)), ])
        1   2   3   4   5   6   7  
    p01 -   10H 11J 12I 9G  9F  -  
    p02 9A  9C  -   -   -   -   -  
    p03 9B  9F  10J -   -   -   -  
    p04 9I  -   -   -   -   -   -  
    p05 12A -   -   -   -   -   -  
    p06 -   -   10B 10F 9D  10E -  
    p07 11B 10E 9A  -   -   -   -  
    p08 10G 11B 11C 12E -   -   -  
    p09 -   -   10I 11D 9H  11C -  
    p10 -   -   -   -   9F  9A  -  
    p11 12F 11I 12D 10I -   -   -  
    p12 -   -   -   10J 11J 11F -  
    p13 10D 11G 11K 11J -   -   -  
    p15 -   -   -   10A 11C 11D -  
    p16 10A 10I -   -   -   -   -  
    p17 10I 11F 12C -   -   -   -  
    p18 -   -   -   -   10F 10G -  
    p19 -   10B 12I 10C 12I 12I -  
    p20 -   -   11D 10E 9A  10J -  
    p21 12B 11H 9H  12A -   -   -  
    p22 10E 9G  11G -   -   -   -  
    p23 10J 9B  12A -   -   -   -  
    p24 10H 10A 10E 10D -   -   -  
    p25 9H  11D 11I 11F -   -   -  
    p26 12C 11E 10C 11C -   -   -  
    p27 12D 12B 11B -   -   -   -  
    p28 11I 12I 11H 11E 12A -   -  
    p29 -   -   12F 11K 11B 10D -  
    p30 -   -   9E  12F 10J 10F -  
    p31 -   -   -   -   12E 12C -  
    p32 11C 11K -   -   -   -   -  
    p33 9G  11J -   9C  10H 12D -  
    p34 -   -   9G  9D  12D 11I 11K
    p35 10F -   -   -   -   -   -  
    p36 12I 12H -   -   12F 9E  -  
    p37 -   -   9D  11G 10G 12H -  
    p38 11E 10J 11F 9A  11G -   -  
    p39 10C 12G -   10B 12C 11G -  
    p40 -   10D 11A 12H 12B 11K -  
    p41 9C  9I  -   -   -   -   -  
    p42 12E 9A  12B -   9C  11B -  
    p43 -   -   -   11I 11I 11A 11E
    p44 9D  9E  -   9F  10I 10A -  
    p45 -   -   -   -   10B 10C -  
    p46 11A -   -   -   -   -   -  
    p47 -   -   -   -   11H 12A -  
    p48 11F 9H  10D -   -   -   -  
    p49 11D 12F 12E -   -   -   -  
    p50 12H 11A 11E 11B 11F -   -  
    p51 -   -   10G 12G 11K 9D  -  
    p52 11H -   -   -   -   -   -  
    p53 11K 12A -   -   -   -   -  
    p54 -   -   -   11A 12G 11H -  
    p55 9E  10G 10H 9H  -   -   -  
    p56 -   -   -   -   9I  9G  -  
    p57 11J 12C 10A 10H -   -   -  
    p58 -   12E 10F 9B  9B  9C  -  
    p59 -   -   -   9E  11D 12F -  
    p60 -   -   -   -   11A 10I 11F
    p61 -   -   -   -   -   9I  -  
    p63 -   -   -   9I  9E  10B -  
    p64 12G 11C 9F  12D -   -   -  
    p65 10B 9D  12G 10G 10A 9H  -  
    p66 11G 12D 9C  -   -   -   -  
    p67 -   10C 9B  12B 11E 11E -  
    p68 9F  -   -   -   -   -   -  
    p69 -   -   9I  9G  10C 9B  -  
    p70 -   10F 12H 12C 10E 12G -  
    p71 -   -   -   -   10D 11J -  
    p72 -   -   -   11H 12H 10H -  

De observat că lipsesc numai doi profesori (p14 și p62), dintre cei 72 (pe orarul original, într-una din zile erau liberi peste măsură de mulți, 18 profesori, dintre care unii figurau pe alte zile cu câte 5 sau 6 ore — v. [1], partea I).

Repetând secvența de reducere a ferestrelor (sau repetând recast() doar pe una sau alta dintre zile) obținem alte orare zilnice, cu altă distribuție a ferestrelor (poate, cu ferestre de numai câte o oră) și poate, cu un număr mai mic de ferestre.

Pare firească, ideea de a reuni într-unul singur, cele trei pachete pe care ne-am bazat mai sus — încât producerea unui orar ar putea reveni la un singur apel care primind încadrarea pe clase a profesorilor, invocă succesiv mount_days(), mount_hours() și recast(). Dar această idee pare bună doar dacă, precum în cazul de față, nu avem de-a face cu lecții cuplate; altfel, dacă avem cuplaje și tuplaje (cum se întâmplă de obicei în școli), atunci sunt necesare anumite operații intermediare și nu prea mai poate fi vorba de "un singur apel" pentru a produce orarul. Separarea pachetelor este justificată și de situații precum aceasta: există deja un orar, dar are cam multe ferestre — atunci n-am avea nevoie de toate cele trei pachete, ci doar de refitgaps.

vezi Cărţile mele (de programare)

docerpro | Prev |