[1] V.Bazon - De la seturi de date și limbajul R, la orare școlare (Google Books)
[2] V. Bazon - Orare școlare echilibrate și limbajul R (Google Books)
Prin programele R din [1] și [2], lecțiile prof|cls
sunt repartizate pe zilele de lucru, apoi lecțiile prof|cls|zl
sunt alocate pe orele 1:7
ale zilei curente (fără a lua prea mult în seamă ferestrele apărute); subliniem că repartizând întâi pe zile — în loc de a aloca direct pe sloturile "zl|ora
" — am urmărit să obținem orare echilibrate: fiecare profesor (și fiecare clasă) are în fiecare zi cam câte un același număr de ore și de regulă, fiecare disciplină apare la clasă cel mult câte o singură dată pe zi.
În final, după obținerea unui orar echilibrat, urmează o etapă mai lungă (totuși, numai circa 6 minute/zi), constând în reducerea pe cât se poate a numărului de ferestre.
Pentru reducerea ferestrelor din orarul zilei curente, întâi se transformă forma normală prof|cls|ora
a orarului, în formatul matriceal: fiecărui profesor i se asociază o linie care înregistrează clasele la care trebuie să intre în fiecare oră, sau "-
" pentru cazul când în ora curentă este liber; de exemplu:
1 2 3 4 5 6 7 Ch4 7B - 10D 9F - 8F - # 4 lecții cu două ferestre, cu șablonul # "*-**-*-" sau binar 0x2D = 45
În "matricea-orară" fiecare clasă apare câte o singură dată pe fiecare coloană 1:n
, unde n
este numărul de ore la clasa respectivă, în ziua curentă. Pentru a elimina o fereastră, se mută o clasă dintr-o coloană orară în alta — ceea ce implică (pentru a păstra unicitatea pe coloană a fiecărei clase) schimburi bilaterale între cele două coloane (aceste schimburi urmează ciclurile Kempe ale grafului valorilor din cele două coloane, adiacente dacă se află pe o aceeași linie; v. [2]).
În prealabil înființasem SBC.Rda
, un "dicționar" care asociază fiecărui șablon de orar individual cu ferestre, o listă de cupluri (h1, h2)
— vizând această semnificație: mutând clasa din coloana h1
în coloana h2
, se asigură eliminarea ferestrei existente inițial în coloana h2
. De exemplu, pentru șablonul redat mai sus avem:
SBC[["*-**-*-"]] <- list(h1 = c(1,6), h2 = c(5,2))
Mop
fiind matricea-orar inițială, după mutarea move_cls(Mop, 1, 5, "7B")
rezultă o nouă matrice-orar, în care Ch4
a scăpat de fereastra existentă inițial în ora a 5-a; în mod implicit, prin mutarea respectivă se vor fi eliminat și alte ferestre sau dimpotrivă, se vor fi produs noi ferestre, la alți profesori — iar problema revine la a căuta asemenea mutări (v. search_better()
din [2]) care diminuează numărul total de ferestre.
Dar formulasem SBC
în mod manual și expeditiv — suficient inițial, pentru a demara punerea la punct a unui mecanism prin care să înlănțuim cumva mutările indicate de SBC
, până ce rezultă un orar cu număr mai mic de ferestre; anume, am înscris într-un fișier liniile SBC[[
<șablon>]] = list(h1=c(), h2=c())
, apoi am deschis fișierul respectiv într-un editor de text și am completat pe rând câmpurile h1
și h2
(rezumându-ne la cele mai directe mutări pentru eliminarea ferestrelor din șablonul curent; v. [3]).
Ne ocupăm acum de generarea automată a unui dicționar SBC
, considerând și alte mutări decât cele înregistrate manual prin [3]:
# cover.R library(tidyverse) h2bin <- as.integer(c(1, 2, 4, 8, 16, 32, 64)) # măştile orelor zilei (7 ore) cnt_hours_gaps <- function(sb) { # sb: şablon binar 1:127, pe orele 1:7 bits <- which(bitwAnd(sb, h2bin) > 0) # rangurile biţilor '1' n <- length(bits) # numărul de ore (lecții la clase) g <- bits[n] - bits[1] - n + 1 # numărul de ferestre c(n, g) } HG <- map_int(1:127, function(S) { ng <- cnt_hours_gaps(S) ifelse(ng[2] > 0, S, 0) }) HG <- HG[HG > 0] # cele 99 șabloane binare cu ferestre SBC <- map(HG, function(S) { L <- bitwAnd(S, h2bin) ik <- range(which(L > 0)) # indecșii lecțiilor H1 <- H2 <- vector("integer") # pentru move_cls() din H1 în H2 for(i in ik[1]:(ik[2]-1)) { if(L[i] > 0) { for(j in (i+1):ik[2]) { if(L[j] == 0) { # va muta L[i] în fereastră H1 <- c(H1, i) H2 <- c(H2, j) } } } else { for(j in (i+1):ik[2]) { if(L[j] > 0) { # va muta L[j] în fereastră H1 <- c(H1, j) H2 <- c(H2, i) } } } } list(h1 = H1, h2 = H2) }) %>% setNames(HG)
În pofida neîncrederii cu care pornisem lucrurile acum vreo trei ani, generarea automată a listei SBC
se dovedește a fi mult mai simplu de instituit, față de formularea manuală evocată în [3]… Iar gama de "mutări corectoare" este acum cam de trei ori mai largă; de exemplu, pentru șablonul exemplificat mai sus acum avem:
> SBC[["45"]] # byte_line(45) este "*-**-*-" $h1 [1] 1 1 3 4 6 3 4 6 # anterior, h1=(1,6) și h2=(5,2) $h2 [1] 2 5 2 2 2 5 5 5
În cadrul mecanismului iterativ pe care l-am instituit pentru reducerea treptată a ferestrelor, alegem la întâmplare (aleatoriu), câte una dintre mutările corectoare. Fiind acum, mai multe mutări pe șablon, decât cele "directe" din [3] — ar trebui probabil să le prioritizăm; de exemplu, pentru șablonul "45" de mai sus, par preferabile mutările (1,5) și (6,2) care "repară" direct cele două ferestre, față de o mutare ca (1,2) care ar elimina o singură fereastră…
Deocamdată observăm doar că avem de peste trei ori mai multe mutări decât în [3]:
> SBC %>% unlist() %>% length() [1] 1404 # mutări corectoare > load("~/24oct/SBC.Rda") # SBC din [3] (folosit ultima dată în ~/24oct/) > SBC %>% unlist() %>% length() [1] 420
Precizăm și funcția byte_line()
, prin care transformăm un șablon, din forma binară în forma literală (mai simplu și elegant, decât prin funcția byte_as_line()
din [3]):
> byte_line function(sb) bitwAnd(sb, h2bin) %>% sapply(., function(b) if(b) "*" else "-") %>% paste0(., collapse = "")
Folosind byte_line()
putem vizualiza orarele individuale cu ferestre, existente pe matricele-orar ale zilelor:
G <- map_dfr(Zile, function(zi) { Oz <- W[[zi]] %>% as.data.frame() # matricea-orar curentă map_dfr(rownames(Oz), function(P) { patt <- sum(h2bin[which(! Oz[P, ] %in% "-")]) if(cnt_holes(patt) > 0) Oz[P, ] %>% mutate(prof = P, SB = byte_line(patt)) }) %>% compact() %>% mutate(zl = zi) }) %>% remove_rownames() %>% arrange(prof, zl)
De exemplu, pe "Biologie":
> G %>% filter(grepl("Bi", prof)) 1 2 3 4 5 6 7 prof SB zl 1 10G 12B 8A - 6B 6G - Bi2 ***-**- Jo 2 9C 8F 6G - 7D - - Bi2 ***-*-- Vi 3 8B 5D - 8G 9D 5F - Bi3 **-***- Mi 4 10D 5B 6D - 8C - - Bi4 ***-*-- Lu 5 12D 6D 11A - 9E - - Bi4 ***-*-- Vi
vedem că Bi2
are două zile cu câte o fereastră, iar ceilalți trei au câte o zi cu câte o fereastră; precizăm că datele respective fac parte din orarul rezultat anterior în "Cazul cel mai simplu al problemei orarului" — în care am probat noul set SBC
, dar (de data aceasta)… cam fără succes: numărul de ferestre n-a putut fi redus mai mult (poate doar, mai repede) decât atunci când foloseam setul SBC
inițial, din [3].
Probabil că avem de modificat cumva și mecanismul search_better()
, pentru a beneficia într-adevăr, de această extindere a gamei SBC
de "mutări corectoare"… Trebuie văzut întâi (ceea ce nu este mai simplu), dacă n-ar fi suficient să prioritizăm mutările, pentru fiecare șablon — alegând câte o mutare corectoare nu chiar la întâmplare, ci în funcție și de anumite probabilități de corectare asociate acestora.
(… „ Ce e rău și ce e bine. Tu te-ntreabă și socoate; ”)
vezi Cărţile mele (de programare)