În [1] ajunsesem la un orar cu respectiv 7 / 11 / 10 / 7 / 6 ferestre pe zi; între timp, repetând programul reduce_gaps.R
(după ce am dublat numărul de iterări în funcția search_better()
și în compensație, am forțat cover_gaps()
să returneze doar o parte aleatorie a reparațiilor curente de ferestre), am ajuns la un orar cu numai 35 de ferestre:
Mai <- readRDS("orar_mai.RDS") # orarul curent sapply(Zile, function(zi) { set_globals(zi) # v. [1] count_gaps(Mai[[zi]]) }) |> print() Lu Ma Mi Jo Vi 5 10 9 6 5 # numărul curent de ferestre, pe fiecare zi (în total, 35)
Numărul de ferestre pe Ma
și Mi
rămâne (practic, oricât am repeta search_better()
) sensibil mai mare decât pe celelalte zile; ar fi de investigat cauzele acestui fapt, reluând în ultimă instanță, repartizarea pe zile a lecțiilor…
Este de subliniat că spre deosebire de orarul original de pe care am dedus datele de încadrare (v. [1]), lecțiile au fost repartizate echilibrat – dar în pofida acestei diferențe majore, avem acum un număr sensibil mai mic de ferestre, decât cel (=55) din orarul original.
De obicei, pentru a remedia ferestrele se renunță la echilibre – acceptând ca un obiect sau altul să aibă două ore la clasă într-o aceeași zi, un profesor sau altul să aibă 2 ore într-o zi și 7 într-o alta, o clasă să aibă 4 ore într-o zi (începând poate nu de la prima, ci de la a doua sau a treia oră a zilei) și 7 într-o alta – dar aceasta nu este calea noastră, deschisă în [1] (am urmărit consecvent o repartizare cât mai uniformă a lecțiilor, pe zile, clase, profesori și obiecte).
În loc de a regândi repartizarea (echilibrată) pe zile, pare totuși „mai simplu” (și ar fi mai folositor) să vedem dacă nu cumva am putea reduce numărul de ferestre pe Ma
și Mi
, doar schimbând în prealabil unele ore (lecții prof|cls|ora
) între aceste două zile (cu grija de a nu dezechilibra sensibil, orarul).
În principiu, pentru a decide ce schimbări de ore între cele două zile ar trebui încercate (în scopul de a reduce numărul de ferestre), ar trebui să plecăm de la profesorii care au ferestre; deci întâi, s-ar cuveni să evidențiem ferestrele existente în orarele zilelor.
Orarele zilelor sunt (în final) matrici care au drept nume de linii numele profesorilor (codificate după disciplină și număr de ore – v. [1]) și drept valori, clasele repartizate profesorilor respectivi în fiecare oră a zilei (sau "-
" pentru fereastră, sau oră liberă):
> str(Mai[["Ma"]]) chr [1:67, 1:7] "7A" "9D" "-" "11B" "10C" "8A" "6A" "-" "11E" "-" "11C" ... - attr(*, "dimnames")=List of 2 ..$ : chr [1:67] "ES2" "Fi5" "Ge3" "TI2" ... # numele profesorilor ..$ : chr [1:7] "1" "2" "3" "4" ... # orele 1..7 ale zilei
Următoarea funcție preia matricea-orar a unei zile, o transformă în data.frame (integrând drept coloană numele de linii ale matricei), apoi adaugă o coloană $SB
pe care, pentru profesorii care au ferestre (și pentru cei angajați în cuplaje), se vor vizualiza șabloanele-orare, folosind vectorul h2bin
(care conține măștile binare ale orelor 1..7) și funcția byte_as_line()
(care transformă un octet într-un șir de caractere '*
' sau '-
') (v. [1]):
emph_gaps <- function(ORR) { Oz <- ORR %>% as.data.frame() %>% mutate(prof = rownames(.), .before=1) %>% mutate(SB = "") rownames(Oz) <- NULL # numele de linii nu mai sunt necesare for(P in Oz$prof) { if(nchar(P) > 3) next # ignoră cuplajele (profesorii "fictivi") MP <- Oz %>% filter(grepl(P, prof)) if(nrow(MP) == 1) { # profesor neangajat în cuplaje patt <- sum(h2bin[which(! MP[1, 2:8] %in% "-")]) %>% byte_as_line() # v. [1] if(grepl(".*\\*-{1,2}\\*.*", patt)) Oz[Oz$prof == P, "SB"] <- patt } else { # profesor angajat în cel puțin un cuplaj patt <- 0L for(i in 1:nrow(MP)) patt <- patt + sum(h2bin[which(! MP[i, 2:8] %in% "-")]) Oz[Oz$prof == P, "SB"] <- byte_as_line(patt) } } Oz %>% arrange(prof) }
Pe orarele individuale rezultate prin programele din [1] putem avea o fereastră, după șablonul parțial "*-*
", sau două ferestre consecutive, după șablonul parțial "*--*
"; deci expresia regulată care depistează orarele individuale cu ferestre este în cazul nostru, ".*\\*-{1,2}\\*.*
" (folosită deja mai sus, pentru cei neangajați în cuplaje).
Următoarea funcție selectează din tabelul returnat de emph_gaps()
, liniile corespunzătoare celor care au ferestre în ziua respectivă:
per_gaps <- function(Oz) Oz %>% filter(grepl(".*\\*-{1,2}\\*.*", SB))
Cele 35 de ferestre existente pe orarul curent reprezintă 3.44% din totalul 1018 al tuturor lecțiilor dintr-o săptămână; or fi ele (zicem noi…) puține – dar este important să vedem și cum sunt repartizate ferestrele, pe profesori și zile:
gaps <- map_dfr(Zile, function(zi) Mai[[zi]] |> emph_gaps() |> per_gaps() %>% mutate(zi = zi, .before=2)) %>% arrange(prof)
prof zi 1 2 3 4 5 6 7 SB 1 Bi1 Vi 12A - - 9E 10E 10C - **-***- 2 En1 Ma 12A 10C 9B - 10B 12D - ***-**- 3 En1 Mi 9B 10B - 12D 10C 9E - **-***- 4 En1 Vi 10C 11A - 9B 10B 9E - **-***- 5 En2 Lu 6D 10D 11B - 8C 9D - ***-**- 6 En2 Jo 8C 9D - 10D 11B 6D - **-***- 7 En3 Ma 10E 12B - 5C 11E 7B - **-***- 8 En4 Ma - 6C - 8B 7A 12E - -*-***- 9 En4 Mi 6B 8A - - 12E 8B - **--**- 10 En5 Lu 5B 5D 11D - 12C - - ***-*-- 11 Fi1 Ma - 6A 12B - 9C 11A - -**-**- 12 Fr1 Ma - 6B 7A - - - - ***-*-- 13 Fr1 Jo - - - - 5B 8B - -**-**- 14 IP1 Ma 10A 12A 11A - 12A - - ***-*-- 15 Is1 Mi 8B 6C 10E - 8A 10D - ***-**- 16 Is1 Jo 8A 11D 8C - 11C 10E - ***-**- 17 Is2 Jo 11E 7A 5D - 5C - - ***-*-- 18 Ma1 Lu 7A 5B 9A - 11A 10A - ***-**- 19 Ma2 Mi 10B 6A - - 9C 7B - **--**- 20 Ma4 Vi - 11B 12B - 5C 8A - -**-**- 21 Mu1 Vi 5B - - - 8A 6B - ***-**- 22 Re1 Ma 5B 6D - 12C 6B - - **-**-- 23 Ro2 Lu 10B 7A - 10A 12D 11B - **-***- 24 Ro2 Mi 12A 7A 12D - 11B 10B - ***-**- 25 Ro3 Lu 12E 12E 9C - 12B 6A - ***-**- 26 Ro3 Jo 12E 6A - 9C 11A 12E - **-***- 27 Ro3 Vi 12B 6A 12E - 9C 11A - ***-**- 28 Ro6 Ma 11C 5B - 5A 5A - - **-**-- 29 Sp1 Ma - 9E - 6B 10D 6A - -*-***- 30 Sp1 Jo - 10A - 10C 10B 9D - -*-***- 31 Sp2 Ma - 5C - 11A 5D 9A - -*-***- 32 Sp3 Mi 11B 8B - - 7A 6D - **--**-
Constatăm că au apărut și situații pe care nu ni le-am fi dorit: doi profesori (En1
și Ro3
) au câte trei zile cu câte o fereastră; trei profesori (En4
, Ma2
și Sp3
) au câte o zi – și se întâmplă că aceasta este la toți, ziua Mi
– cu câte două ferestre consecutive (și se întâmplă că în aceleași ore ale zilei, a treia și a patra – după șablonul "**--**-
").
Să observăm însă că dacă vom relua search_better()
plecând de la orarele obținute mai sus, vom obține noi orare, cam cu același număr de ferestre zilnice, dar cu alte repartizări pe profesori a ferestrelor, poate mai convenabile…
Pe de altă parte, dacă vedem lucrurile global, la nivelul întregii săptămâni:
table(gaps$prof) |> as.data.frame() |> arrange(desc(Freq)) |> print()
putem aprecia că situația este totuși apropiată de „normal”: 14 profesori au doar câte o singură fereastră pe săptămână, 6 au câte două ferestre și numai doi, ajung la 3 ferestre pe săptămână; ceilalți 45 de profesori își fac orele fără nicio fereastră.
Iar aceste proporții s-ar îmbunătăți, dacă am reuși să reducem numărul de ferestre pe Ma
și Mi
(care este prea mare, față de celelalte zile).
Pentru un prim experiment, să considerăm liniile 8 și 9 din tabelul redat mai sus:
prof zi 1 2 3 4 5 6 7 SB 8 En4 Ma - 6C - 8B 7A 12E - -*-***- 9 En4 Mi 6B 8A - - 12E 8B - **--**-
Să ne imaginăm că "6C
" ar figura la En4
nu Ma
în ora a doua (cu fereastră în a treia oră), ci Mi
în ora a treia (sau în a patra); atunci En4
ar scăpa de două ferestre (este aproape sigur, având în vedere cum funcționează search_better()
, că acele 3 ore rămase pe Ma
vor rămâne așezate compact). Este drept însă că astfel, En4
(și poate, încă vreun profesor) ar căpăta o distribuție orară ușor dezechilibrată (3/5 în loc de 4/4)…
Am mutat manual 6C
din coloana orară "2" a zilei Ma
în coloana "3" a zilei Mi
– imitând funcția move_cls()
(ținând seama că acum mutarea clasei are loc între coloane orare din zile diferite și nu ale unei aceleiași zile, ca în [1]). Pe orarul rezultat astfel, am lansat search_better()
și apoi am evidențiat prin emph_gaps()
noua situație a ferestrelor:
Ma prof 1 2 3 4 5 6 7 SB Mi prof 1 2 3 4 5 6 7 SB 1 En3 12B 5C 7B - 11E 10E - ***-**- 1 Ch1 10C 12E 12A - 7C - - ***-*-- 2 En5 5D 11C 5B - 11D - - ***-*-- 2 En1 10B 9B - 9E 12D 10C - **-***- 3 Fi1 6A 11A 9C - 12B - - ***-*-- 3 En2 6D 11B 8C - 9D 10D - ***-**- 4 GF4 - - - - - 11E - -**-**- 4 En4 6C 8A 6B - 12E 8B - ***-**- 5 IP1 - 10A - 12A 12A 11A - -*-***- 5 Is1 8B 10D - 10E 8A 6C - **-***- 6 Is1 10E 11B - 12E 6B 10C - **-***- 6 Is2 9E 10A 5B - 7A - - ***-*-- 7 IT1 - 5B - 5C 5A 6D - -*-***- 7 Re2 5C 9E - - 11E 12A - **--**- 8 Ma5 6B 10C - 12D 9E - - **-**-- 9 Ro1 5C 5D - 8C 10C 11D - **-***- 10 Ro4 7B 9E - 8A 6D - - **-**--
Pe Ma
avem tot 10 ferestre (toate de câte o singură oră – fie a treia, fie a patra), dar cu altă repartizare pe profesori, față de cea existentă pe orarul inițial; pe Mi
au rezultat totuși 8 ferestre, în loc de cele 9 existente inițial (iar numărul de profesori cu câte două ferestre consecutive s-a redus de la 3, la 1 – anume la profesorul de Religie Re2
).
Experimentul modest evocat mai sus arată că în final, am putea echilibra orarul și în privința numărului de ferestre pe zi, folosind o funcție analogă cu move_cls()
din [1], prin care să mutăm anumite lecții prof|cls|ora
între anumite coloane orare (cel mai probabil, a doua și a treia sau a patra) din orarele a două zile pe care numărul curent de ferestre a rămas prea mare, față de celelalte zile. Rămâne să formulăm o versiune corespunzătoare de move_cls()
și să decidem asupra unei liste de mutări pe care să le încercăm succesiv, prin search_better()
… Deasemenea, va trebui să vedem în ce măsură mutările respective perturbă echilibrele existente pe orarul inițial.
vezi Cărţile mele (de programare)