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

Cazul cel mai simplu, al problemei orarului

graf | limbajul R | orar şcolar
2024 dec

În [3] și [2] am dezvoltat programe $\mathbf{R}$ prin care lecțiile prof|cls sunt repartizate echilibrat pe zilele de lucru, apoi lecțiile prof|cls|zl sunt alocate pe orele 1:7 ale zilei, iar în final se reduc ferestrele rezultate pe orarul astfel constituit.
Pentru a pune la punct programele respective, am analizat și orare postate pe site-urile unor școli reale — orare constituite "cap-coadă" prin aplicația de tip point-and-click de la asctimetables; singurul scop al postării orarelor respective era acela practic (tabelând elevilor și profesorilor școlii ce au de făcut în fiecare zi și oră) și totdeauna, a trebuit întâi să detectăm și să extragem datele de pe tabelele vizuale respective (confruntându-ne mereu cu lipsurile specifice formulării vizuale sau manuale; v. [1] etc.).

Vom considera acum și cazul cel mai simplu, scutit de conjuncturi: avem o școală fictivă "SF", care are suficiente săli de clasă (încât poate funcționa într-un singur schimb), destule săli de sport și laboratoare dedicate (încât nu avem restricții privind alocarea lecțiilor de "Sport" sau de "Informatică", sau de limbi străine, etc.); presupunem deasemenea că sunt disponibili pe piață destui profesori pentru fiecare disciplină, iar elevii școlii au fost astfel distribuiți pe clase încât să nu fie necesară invenția lecțiilor "pe grupe", la limbile străine (și nici la "Muzică" și "Desen"!…).

În aceste premise ideale, realizarea orarului pentru "SF" va deveni mult mai simplă decât în cazurile din realitate, fiindcă nu mai este necesar să inventăm și să ținem seama de cuplaje, tuplaje și simultane; dar… avem de văzut cât de simplă (adaptând programele noastre din [3]), fiindcă implicit (de vreme ce avem suficient spațiu și resurse umane), avem un număr foarte mare de clase și profesori (în plus, trebuie să vedem câți profesori sunt necesari pentru fiecare disciplină și să-i încadrăm echitabil pe clase).

Planurile de încadrare

Plecăm de la „planul-cadru de învățământ” (v. edu.ro), dar fiindcă vizăm orare și nu „politica educațională”, nu distingem între disciplinele "obligatorii" și cele "opționale". Deasemenea, nu distingem după "arii curriculare"; de fapt, separarea în arii disjuncte a disciplinelor este un act artificial cam meschin, de natură sindicală, care induce ideea complet falsă, contrară istoriei lucrurilor, că "una nu are de-a face cu alta" — de exemplu, cel mai frapant, că "Informatică" nu are de-a face cu "Matematică" (nici cu limbile și nici cu… "română"; pe tabelele elaborate de instituții încă se scrie "ROMANA").

Considerăm o încărcătură maximală, câte 8 clase pe fiecare nivel 5:12 (în total, 64 de clase); de la clasa a 9-a încolo, distingem după "PROFIL" și "SPECIALIZARE" (de care depinde numărul de ore alocat disciplinelor, la clase de același nivel):

CLS <- lapply(5:12, function(i) paste0(i, LETTERS[1:8])) %>% unlist()
  Profil                                 Specializare  Nr_clase (nivel 9:12)
    real matematică-informatică, intensiv informatică        2  (AB)
    real         științe ale naturii, bilingv engleză        1  (C)
    real                          științe ale naturii        1  (D)
    uman             științe sociale, bilingv engleză        1  (E)
    uman                  filologie, bilingv franceză        1  (F)
    real        științe ale naturii, în limba germană        1  (G)
    uman                                    filologie        1  (H)

Pentru "discipline" facem tot soiul de simplificări (profitabile pentru problema orarului); de exemplu, considerăm că "Educație muzicală | plastică | artistică | vizuală" cad la toate clasele unde apar, în seama unor profesori de "Muzică" ("Mz") și de "Desen" ("Ds"); desemnăm prin "ES" (pentru "Educație socială") obiecte ca "Economie", "Educație antreprenorială", "Educație juridică", "Filosofie", "Logică", "Tehnici de argumentare", "Psihologie", "Sociologie" (care pe filiera "TEORETCĂ" au cel mult o oră/clasă); ignorăm "Istoria Franței", "Geografia Franței", "Istoria și tradițiile minorităților", "Repere geografice si culturale ale Germaniei", "Literatură universală", "Limbă și civilizație italiană" etc. — adăugând orele respective profesorilor care fac "Franceză" (Fr), "Germană", sau "Latină", "Română" etc.

Până la urmă ne-am fixat aceste 17 discipline principale:

Dsp <- readRDS("discipline.RDS")
              disc cod                disc cod
    1     Biologie  Bi      10     Istorie  Is
    2       Chimie  Ch      11      Latină  La
    3        Desen  Ds      12  Matematică  Mt
    4      Engleză  En      13      Muzică  Mz
    5       Fizică  Fi      14     Religie  Rg
    6     Franceză  Fr      15      Română  Ro
    7    Geografie  Ge      16 Socio-umane  SU
    8      Germană  Gr      17       Sport  Sp
    9  Informatică  In      

În "frame_64.RDS" avem încadrarea disciplinelor pe fiecare clasă:

SF <- readRDS("frame_64.RDS")
    data.frame:	916 obs. of  3 variables:
     $ cls: chr  "10A" "10A" "10A" "10A" ...
     $ dsp: Factor w/ 17 levels "Bi" "Ch" "Ds" ..: 1 2 3 4 5 6 7 9 10 12 ...
     $ ore: num  2 2 1 2 3 2 1 5 1 4 ...
> SF %>% filter(cls %in% c("10A", "12G"))  # exemplificare
       cls dsp ore         cls dsp ore
    1  10A  Bi   2      16 12G  Bi   2
    2  10A  Ch   2      17 12G  Ch   2
    3  10A  Ds   1      18 12G  En   2
    4  10A  En   2      19 12G  Fi   3
    5  10A  Fi   3      20 12G  Ge   1
    6  10A  Fr   2      21 12G  Gr   6
    7  10A  Ge   1      22 12G  In   1
    8  10A  In   5      23 12G  Is   1
    9  10A  Is   1      24 12G  Mt   4
    10 10A  Mt   4      25 12G  Rg   1
    11 10A  Mz   1      26 12G  Ro   4
    12 10A  Rg   1      27 12G  SU   1
    13 10A  Ro   3      28 12G  Sp   1
    14 10A  SU   2
    15 10A  Sp   2

Avem această distribuție a numărului de ore pe clasă, pentru fiecare disciplină:

Tot <- addmargins(table(SF[c('dsp', 'ore')]), 1)
    dsp     1   2   3   4   5   6   7
      Bi   30  32   0   0   0   0   0  # total: 30 + 32*2 = 94 ore de "Biologie"
      Ch   10  28   4   0   0   0   0
      Ds   54   0   0   0   0   0   0
      En    0  47   5   2   2   8   0
      Fi    0  30  20   0   0   0   0
      Fr    0  50   6   0   0   0   0
      Ge   46  17   1   0   0   0   0
      Gr    0   0   0   6   1   1   0
      In   43  10   3   0   2   2   4
      Is   34  24   6   0   0   0   0
      La   19   3   0   0   0   0   0
      Mt    0   8   1  47   4   0   0
      Mz   50   0   0   0   0   0   0
      Rg   64   0   0   0   0   0   0
      Ro    0   0  15  45   1   1   2
      SU   11  12  37   2   2   0   0
      Sp   25  39   0   0   0   0   0
      Sum 386 300  98 102  12  12   6  # total: 386+300*2+98*3+... = 1862 ore

Formulăm un dicționar "Dno" cu numărul de ore pe fiecare disciplină:

dot_prod <- function(M)  # produsul scalar
    as.vector(t(M[, 1]) %*% M[, 2])
tt <- Tot %>% as.data.frame() %>% mutate(ore = as.integer(ore))
total_ore <- function(D)
    tt %>% filter(dsp == D) %>% select(-dsp) %>% dot_prod()
Ld <- levels(SF$dsp)
Dno <- map(Ld, total_ore) %>% 
       setNames(Ld) %>% unlist() %>% sort()
     La  Gr  Mz  Ds  Rg  Ch  Ge  Bi  Is  Sp  Fr  Fi  In  SU  En  Mt  Ro 
     25  35  50  54  64  78  83  94 100 103 118 120 122 164 175 227 250 

Considerăm norma săptămânală de lucru de 16-18 ore. Pentru "Desen" de exemplu, sunt necesari Dno["Ds"]/18=54/18= 3 profesori (și îi indicăm prin Ds1, Ds2 și Ds3). Pentru "La" ("Latină" și complemente ca "Limbă și civilizație italiană") ar fi suficient un profesor, dar acesta ar avea 7 ore suplimentare față de normă; vom vedea dacă am putea adăuga încă niște ore de "La", la unele clase, încât să constituim două norme.

Să vedem câte ore pe săptămână are fiecare clasă:

tot_ore <- function(K)
    SF %>% filter(cls == K) %>% count(ore) %>% dot_prod()
Kor <- map(CLS, tot_ore) %>% unlist() %>% setNames(CLS)
 5A  5B  5C  5D  5E  5F  5G  5H  6A  6B  6C  6D  6E  6F  6G  6H  7A  7B  7C  7D 
 25  25  25  25  25  25  27  25  27  27  27  27  27  27  30  27  30  30  30  30 
 7E  7F  7G  7H  8A  8B  8C  8D  8E  8F  8G  8H  9A  9B  9C  9D  9E  9F  9G  9H 
 30  30  33  30  30  30  30  30  30  30  32  30  32  32  33  30  31  31  32  29 
10A 10B 10C 10D 10E 10F 10G 10H 11A 11B 11C 11D 11E 11F 11G 11H 12A 12B 12C 12D 
 32  32  33  30  32  32  31  30  29  29  28  28  28  28  29  28  29  29  28  28 
12E 12F 12G 12H
 28  28  29  28 

Am putea adăuga câte o oră de "La" unor clase care acum au sub 30 de ore, anume claselor a 5-a și a 11-a:

La <- data.frame(cls = c(CLS[1:8], CLS[49:56]), dsp="La", ore=1L)
SF <- rbind(SF, La); saveRDS(SF, "frame_64.RDS")

Acum pe "La" avem 25+16=41 de ore, încât vor fi necesari doi profesori La1 și La2 (cu 20 și 21 de ore); le rămâne desigur, să găsească ceva complementar cu "Latină", util de făcut la clasele respective (să observăm că a 5-a fac deja câte două ore de limbi străine, "Engleză" și "Franceză" de obicei; să fie vreo contradicție cu "politica educațională", dacă am cere să facă și ceva complementar limbii române?).

Obs.1 Unele dintre clasele cărora le-am "adăugat" mai sus câte o oră de "La" aveau deja una sau chiar două ore de "La" și acum, în loc să avem cumulat două, respectiv trei ore, avem câte două linii cu "La" la acele clase (cea veche și cea adăugată); acest defect s-a manifestat mai târziu (în funcția norming(), redată mai jos) și l-am corectat ad-hoc.

Pentru câteva discipline ("Religie", "Desen"), numărul de ore nu variază de la o clasă la alta — încât nu prea are importanță care clase le repartizăm unuia sau altuia dintre profesorii de pe disciplina respectivă. Însă dacă numărul de ore pe o disciplină variază după clasă, atunci avem de văzut care clase le repartizăm profesorilor respectivi, încât aceștia să cumuleze echitabil, norme de lucru apropiate.

Pentru a repartiza clasele, pe o disciplină sau alta, profesorilor respectivi, avem nevoie de următoarea listă de dicționare, "Cdo":

cls_ore <- function(D) {
    Cls <- SF %>% filter(dsp == D) 
    Cls %>% pull(ore) %>% setNames(Cls %>% pull(cls))
}
Cdo <- map(Ld, cls_ore) %>% setNames(Ld)
> Cdo[["Gr"]]  # exemplificare
    10G 11G 12G  5G  6G  7G  8G  9G 
      4   4   6   4   4   4   4   5 

Pentru fiecare disciplină, Cdo indică numărul de ore pe acea disciplină, la fiecare clasă. Pentru cazul "Gr" exemplificat mai sus, profesorii Gr1 și Gr2 vor primi clasa 12G (cu 6 ore) și respectiv 9G (cu 5 ore) și câte 3 clase a câte 4 ore (încadrându-i astfel pe 18 și respectiv 17 ore).

Cum încadrăm profesorii pe clase

Dno[[D]] ne dă numărul total de ore pe disciplina D; raportându-l la "Norma" de 18 ore (sau poate mai bine, 19 sau 20), obținem numărul np de profesori necesari pentru disciplina respectivă. Am avea de partiționat mulțimea claselor la care se face disciplina D în np submulțimi, astfel încât suma orelor D pe clasele dintr-o aceeași submulțime să fie în jurul valorii Norma; probabil, am putea rezolva și folosind vreun pachet de "programare în numere întregi" — dar avem o soluție directă, mai simplă (și… similară cu "etichetarea lecțiilor cu Zile" însușită deja în [3]).

Definim un "tabel" având drept nume de linie clasele care fac disciplina D, cu două coloane h | prof, unde h înregistrează numărul de ore pe disciplina D pentru fiecare clasă, iar prof etichetează liniile repetând consecutiv secvența celor np profesori:

norming <- function(D, Norma=18) {
    S <- Cdo[[D]]  # numărul de ore D, pe fiecare clasă
    P <- data.frame(h = as.integer(S), prof = "")
    rownames(P) <- names(S)
    np <- Dno[[D]] %/% Norma  # estimează numărul de norme
    Prof <- paste0(D, 1:np)  # profesorii pe disciplina D
    P %>% mutate(prof = rep_len(Prof, nrow(.))) %>%
          mutate(prof = factor(prof))
}

Obs.2 Pentru fiecare clasă trebuie să avem o singură linie pentru D (nu două, cum am semnalat în Obs.1) — altfel norming() eșuează, găsind linii distincte cu același "nume de linie" (și apoi, nu putem eticheta cu profesori distincți lecții pe aceeași disciplină, la o aceeași clasă).

Funcția următoare analizează setul de date produs de norming() și listează pe fiecare profesor, clasele (și numărul de ore/clasă) care i-au fost repartizate:

frame_inf <- function(Pdf) {  # Pdf este setul produs de norming()
    D <- substr(Pdf$prof[1], 1, 2)  # disciplina pe care s-a aplicat norming()
    S <- Cdo[[D]]
    Prof <- levels(Pdf$prof)  # etichetele montate de norming() 
    map(Prof, function(X) {
        pp <- Pdf %>% filter(prof == X)
        S[rownames(pp)]  # clasele cu aceeași etichetă (ale aceluiași profesor)
    }) %>% setNames(Prof)
}

Ilustrăm pentru D="Ro", cum putem folosi aceste funcții:

ro <- norming("Ro", 20)  # print(ro)
iro <- frame_inf(ro)  
sapply(iro, sum) %>% print()
     Ro1 Ro10 Ro11 Ro12  Ro2  Ro3  Ro4  Ro5  Ro6  Ro7  Ro8  Ro9 
      22   22   19   22   25   22   24   19   19   18   20   18

Prin etichetarea întreprinsă în norming() (cu Norma=20) ne-au rezultat 12 profesori, cu cel puțin 18 și cel mult 25 de ore fiecare (cu Norma=18 rezultau 13 profesori, dintre care unul cu numai 15 ore). Observând clasele celor cu prea multe ore, putem echilibra (mai echitabil) încadrările respective:

> iro[["Ro2"]]  # încadrarea inițială, pe 25 de ore, a lui Ro2
    10B 11F  5B  6F  8B  9F 
      3   6   4   4   4   4 
> iro[["Ro4"]]
    10D 11H  5D  6H  8D  9H 
      3   5   4   4   4   4 
ro["10D", 2] <- "Ro9"  # mută 10D (cu 3 ore) de la Ro4 la Ro9
ro["9F", 2] <- "Ro7"  # reetichetează 9F (cu 4 ore), din Ro2 în Ro7
iro <- frame_inf(ro)
sapply(iro, sum) %>% print()
     Ro1 Ro10 Ro11 Ro12  Ro2  Ro3  Ro4  Ro5  Ro6  Ro7  Ro8  Ro9 
      22   22   19   22   21   22   21   19   19   22   20   21 

Următoarea funcție înscrie în SF profesorii disciplinei respective:

SF <- SF %>% mutate(prof="")  # dacă n-avem deja, coloana SF$prof
prof_setting <- function(iD) {  # iD este lista returnată de frame_inf()
    D <- substr(names(iD)[[1]], 1, 2)
    walk(names(iD), function(P) {
        wh <- which(SF$dsp == D & SF$cls %in% names(iD[[P]]))
        SF[wh, "prof"] <<- P
    })
}
prof_setting(iro)  # înscrie profesorii de "Ro"
saveRDS(SF, "frame_64.RDS")
> slice_sample(SF %>% filter(dsp=="Ro"), n=4)  # ilustrare
      cls dsp ore prof
    1  5G  Ro   4  Ro7
    2 10G  Ro   3  Ro7
    3 10E  Ro   4  Ro5
    4  6D  Ro   4 Ro12

Analog, pentru D="Mt" obținem o încadrare cu 11 profesori, care după o mică echilibrare arată (sintetic, eludând clasele repartizate) astfel:

     Mt1 Mt10 Mt11  Mt2  Mt3  Mt4  Mt5  Mt6  Mt7  Mt8  Mt9 
      21   20   19   20   22   21   21   22   21   20   20 

La fel rezultă încadrările pe toate celelalte discipline. În total, avem 95 de profesori:

> SF$prof %>% unique() %>% length()
[1] 95  # profesori

Putem reda încadrarea profesorilor la clasa 9A de exemplu, astfel:

a9 <- SF %>% filter(cls=="9A") 
a9 %>% pull(ore) %>% setNames(a9 %>% pull(prof))
    Bi5 Ch3 Ds2 En3 Fi1 Fr2 Ge1 In3 Is2 Mt9 Mz1 Rg3 Ro9 SU5 Sp2 
      2   2   1   2   3   2   1   6   1   4   1   1   4   1   1 

sum(SF$ore) ne dă numărul tuturor lecțiilor care urmează să se desfășoare în cursul săptămânii, anume 1876 de lecții.

Setul tuturor lecțiilor

Avem de alocat pe zile (și apoi, pe orele zilei) fiecare lecție în parte; deci în loc de o linie ca "12F En 4 En4", trebuie să vedem 4 linii (identice) "12F En4" (putem omite disciplina, fiindcă aceasta se deduce totdeauna din numele profesorului), urmând să etichetăm prima linie, de exemplu, cu "Lu", a doua cu "Ma" ș.a.m.d. (însemnând că profesorul En4 face câte o lecție la clasa 12F în zilele Lu, Ma etc.).

Multiplicând fiecare linie din SF după valorile din coloana ore (prin funcția uncount()), obținem setul tuturor lecțiilor prof|cls:

SF <- SF %>% uncount(ore) %>% select(prof, cls)
    'data.frame':	1876 obs. of  2 variables:
     $ prof:  chr  "Bi1" "Bi1" "Ch1" "Ch1" ...
     $ cls : chr  "10A" "10A" "10A" "10A" ...
saveRDS(SF, "lessons.RDS")

Cele 1876 lecții sunt distribuite celor 95 de profesori pe câte cel puțin 16 ore (la un singur profesor) și cel mult 22 ore (la 9 profesori), astfel:

> table(SF$prof) %>% as.integer() %>% table()
    16 17 18 19 20 21 22 
     1  3 15 18 30 19  9 

82 de profesori au între 18 și 21 de ore, iar 30 de profesori au câte 20 de ore.

Teoretic, față de norma de 18 ore ar fi trebuit să avem 1876/18=104 profesori — dar aceștia n-ar putea fi încadrați toți, pe câte exact 18 ore; în loc să considerăm profesori cu mai puțin de 18 ore (doar 4 profesori, au 16 sau 17 ore), am preferat să adăugăm până la 4 ore suplimentare, de profesor.

Repartizarea echilibrată a lecțiilor pe zilele de lucru

Funcția mount_days_necup() din programul "by_days.R" (din [2]) operează cu eventuale reluări, pe lista claselor: pentru fiecare clasă, într-o ordine aleatorie, se consideră subsetul Q al lecțiilor acesteia și prin labels_to_class(Q) se încearcă o etichetare cu Zile a liniilor respective care să mențină, față de etichetările pe clasele anterior întâlnite, omogenitatea distribuției pe zile a lecțiilor fiecăruia dintre profesorii implicați.
În cazul când prin etichetarea curentă, unul dintre profesorii implicați capătă o distribuție în care numărul de ore pe zi diferă cu mai mult de 2 de la o zi la alta — se încearcă (prin permutare) o altă etichetare a lecțiilor clasei curente; dacă oricum am ordona etichetele Zile pe lecțiile din Q, apare mereu câte o distribuție individuală neomogenă — atunci este semnalat eșecul către funcția apelantă mount_days_necup(), iar aceasta va relua de la capăt, schimbând însă ordinea parcurgerii claselor.

În următorul program nu ne mulțumim cu primul rezultat, ci invocăm repetat mount_days_necup() până când rezultatul este omogen (sau cvasi-omogen) și în privința numărului total de ore/zi:

library(tidyverse)
LSS <- readRDS("lessons.RDS")  # 1876 lecții prof|cls
Zile <- c("Lu", "Ma", "Mi", "Jo", "Vi")
perm_zile <- readRDS("lstPerm47.RDS")[[2]]  # permutările de 1:5 (zile)
source("by_days.R")  # mount_days_necup() (v. [2] sau [3])

prnTime <- function(S="")  # pentru a estima și afișa timpii de execuție
    cat(strftime(Sys.time(), format="%H:%M:%S"), S)

while(TRUE) {
    prnTime()
    R1 <- mount_days_necup(LSS) # etichetează cu zile, pe clase
    prnTime("\n")
    s1 <- addmargins(table(R1[c('prof','zl')]))["Sum", 1:5] %>%
          as.vector()  # total ore/zi
    print(s1)
    if(diff(range(s1)) <= 2) break;
}

Redăm cursul execuției programului ('*' marchează clasa curentă; o secvență de 64 '*' corespunde parcurgerii cu succes a tuturor celor 64 de clase):

03:16:35 **************************************************************** 03:18:39 
    [1] 373 373 374 380 376  # totalul de lecții/zi este  neomogen
03:18:39 **************************************************************** 03:19:51 
    [1] 379 373 373 375 376
03:19:51 **************************************************************** 03:21:54 
    [1] 379 375 377 375 370
03:21:54 ********************/  # eșec; se reia procesul
**************************************************************** 03:24:49 
    [1] 372 379 375 373 377
03:24:49 **************************************************************** 03:25:59 
    [1] 375 379 372 373 377
03:25:59 **************************************************************** 03:27:01 
    [1] 376 376 375 374 375  # distribuție cvasi-omogenă a totalului de ore
> saveRDS(R1, "R1.RDS")

În R1.RDS avem o repartizare pe zile a tuturor celor 1876 de lecții, care este echilibrată: pentru fiecare profesor, distribuția pe zile a lecțiilor sale este cvasi-omogenă (cu diferență de cel mult două ore, între o zi și alta); pentru fiecare clasă, distribuția pe zile a lecțiilor ei este omogenă; iar cele 1876 de lecții sunt distribuite cvasi-omogen, pe zilele de lucru.

Ca exemplu, redăm distribuția pe zile a lecțiilor de Biologie:

  prof             Lu                Ma              Mi                Jo             Vi
   Bi1  10A 12G 7C 7H  11C 12A 6A 6F 8E      10A 12G 6F     10F 11C 6A 9B       7C 7H 9B
   Bi2      11D 6B 7D       10B 11D 12H   10B 10G 9C 9H  10G 12B 6B 6G 8A    6G 7D 8F 9C
   Bi3     10C 12C 9D         12C 6C 7E  5D 5F 8B 8G 9D     10H 11F 6C 6H   10C 5A 6H 7E
   Bi4   10D 5B 6D 8C  10D 11G 5G 7A 7F       11G 7F 8H         12D 5E 7A  11A 12D 6D 9E
   Bi5    5C 7G 8D 9G         11H 9A 9F       10E 6E 9A      11B 7B 7G 9G   12F 5H 6E 7B

Dintre acești 5 profesori, Bi5 are o distribuție omogenă (cu 3 sau 4 ore/zi); ceilalți au distribuții cvasi-omogene (iar pentru totalul orelor de Bi rezultă o distribuție suficient de echilibrată: 18 19 18 20 19).

În [4] am definitivat un set de funcții pentru ajustarea interactivă a distribuțiilor individuale; n-ar fi greu să le adaptăm (eliminând termenii care țin de cuplaje) și să le folosim acum pentru a omogeniza distribuțiile cvasi-omogene existente — dar nu ne mai ocupăm aici, de acest aspect.

Montarea orelor pe lecțiile unei zile

Reluăm din [4]-IV programele "between.R", "mount_h1.R" și "bindHours.R", eliminând însă toate referirile la cuplaje și tuplaje (iar aici evităm să mai prezentăm logica și demersurile specifice acestor programe).
După ce separăm lecțiile pe fiecare zi, lansăm:

> source("bindHours.R")
    12:00:24 
    Lu 97 încercări 12:00:32  
    Ma 155 încercări 12:00:43  
    Mi 99 încercări 12:00:50  
    Jo 22 încercări 12:00:52  
    Vi 21 încercări 12:00:54  
> saveRDS(ORR, "Orar1.RDS")

În lista ORR ne-au rezultat (în vreo 30 sec., de această dată) orarele prof|cls|ora pe fiecare zi; de exemplu, pentru lecțiile zilei Lu avem:

> glimpse(ORR[["Lu"]])
Rows: 376
Columns: 3
$ prof  Ds1, In1, Is5, Bi1, Fi1, Fr1, Mt1, Mt2, Ro2, Ch2, Ds2, En2, In2, ...
$ cls  "10A", "10A", "10A", "10A", "10A", "10A", "10A", "10B", "10B", ...
$ ora   3, 1, 6, 2, 4, 5, 7, 2, 1, 3, 4, 5, 6, 7, 4, 1, 3, 5, 2, 6, 1, 2,...

Din tabelul sumar oferit de glimpse(), vedem că 10A are Lu 7 ore: prima cu In1, a doua cu Bi1, a treia cu Ds1 ș.a.m.d.

Dacă trecem orarele de la "forma normală" la "matricea-orară" (prin funcția hourly_matrix(), v. [4]), putem constata (vizual) că pe fiecare zi avem foarte multe ferestre (în jur de 70); dar, conform condițiilor impuse în mount_h1(), pe ziua curentă fiecare profesor are cel mult două ferestre.

Reducerea numărului de ferestre

Anterior (v. [2], [4]) am constituit un set de funcții care asigură eliminarea unor ferestre, prin mutări de clase între câte două coloane orare; eliminăm secvențele care vizează cuplajele și tuplajele și putem folosi funcțiile respective astfel:

# xgaps.R
source("Gaps.R")  # v. [2]
ORR <- readRDS("Orar1.RDS")  # lista orarelor zilnice, cu multe ferestre
Kls <- ORR[[1]]$cls %>% unique()
mORR <- map(ORR, hourly_matrix)  # transformă în "matrice-orar"
W <- list()  # pentru orarele cu număr redus de ferestre
for(zi in Zile) {  # Zile[1]
    OZ <- mORR[[zi]]  # matricea orară a zilei
    NG1 <- count_gaps(OZ) # numărul inițial de ferestre
    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
    W[[zi]] <- orr[order(rownames(orr)), ]  
    prnTime("\n") 
}

De exemplu, pentru ziua Lu reducerea decurge cam așa (diferind de la o execuție la alta):

> source("xgaps.R")
06:23:17  (70 ferestre)
69  67  66  64  63  62  61  59  58  57  56  55  54  52  51  50  49  48  47  46
  45  44  43  42  41  40  38  37  36  35  34  33  32  31  30  29  * 29  28  * 28  
  * 28  * 28  * 28  * 28  06:29:29 

Dar ferestrele zilei curente n-au putut fi reduse la mai puțin de 27:

> sapply(W, count_gaps)
    Lu Ma Mi Jo Vi 
    28 29 27 27 28   # ferestre rămase

Anterior, pentru orare constituite pe încadrări ale unor școli reale, programul reducea numărul de ferestre sub 5% din totalul orelor; dar acum ne-au rămas 139 de ferestre, adică 7.4% din totalul 1876 de ore (speram să fie numai vreo 0.05*1876=94 de ferestre).
Sunt două deosebiri, care ar explica situația: aveam cel mai mult 42 de clase (v. [3]), iar acum avem 64 de clase; aveam un număr important de profesori cu ore puține (sub 12 ore/săptămână), iar acum "puține" sunt 16 sau 17 ore și numai în 4 cazuri, toți ceilalți 91 de profesori având câte cel puțin 18 ore (altfel spus, "densitatea" încadrării este acum mult mai mare decât în cazurile "reale", astfel că avem mult mai puține posibilități de acoperire a ferestrelor).
În realitate, profesorii vor ore suplimentare (plătite); iar existența orelor suplimentare (la majoritatea profesorilor, în cazul de față) compensează într-o anumită măsură, suportarea unor ferestre (cel mult două pe zi, pentru unii profesori, în general diferiți de la o zi la alta).

vezi Cărţile mele (de programare)

docerpro | Prev | Next