[1] Chestiunea cuplajelor existente în orarul şcolar (I - VI)
Ne-am format îndelung trei obiceiuri complementare, în această ordine: obiceiul de a greşi (nebanal şi nici grosolan), de a verifica şi de a rescrie lucrurile. Greşala promite, dacă nu te fereşti mereu şi dacă îi accesezi acareturile: a înţelege că ai greşit; a depista (în fel şi chip) unde anume; a înţelege de ce ai greşit; a stabili să nu repeţi greşala. Greşala este în fond, sămânţa progresului (exceptând desigur, lumea cea plină de greşeli ireparabile).
Experimentând suficient vrecast.R
(v. [2] şi [1] - IV – vizate mai jos prin „anterior”), am putut constata diverse defecte şi chiar greşeli; prin urmare… avem iarăşi, de rescris.
Am reţinut câteva greşeli, corectate între timp (cum vom arăta mai încolo, rescriind programul); redându-le acum, post-factum, recunoaştem încă o dată greşala de a nu ne fi folosit de git
, cum s-ar cuveni când lucrezi la un program vast: ne cam chinuim acum cu reconstituirea unor lucruri petrecute în versiuni vechi ale programului.
1.
Pentru unul dintre orare, ferestrele au fost reduse succesiv la 23, 17, 12 ş.a.m.d.; cam după 5 minute, s-a ajuns la 8 ferestre şi după încă vreo 5 minute, la rezultatul final, cu 6 ferestre:
> source("vrecast.R") # o versiune veche, a programului [1] 33 # numărul iniţial de ferestre [1] 18:49:20 # momentul începerii execuţiei 23 17 12 11 10 9 8 7 6 [1] 6 # numărul de ferestre, pe parcurs (în final, 6) 1 2 3 4 5 6 7 p01 11A 10A - - - 12A - p01p02 - - 12A 11A 9A - - # p01 şi p02, împreună (ambii, fără ferestre) p02 11D 11B - - - 9A - p06 - - 11D 12D 9F 12F - p06p33 9A 10E - - - - - # p06 şi p33 nu au ferestre p33 - - 8G 9E - - - p08 - 10F 9F - 11B 12D - # p08 şi p25 nu au ferestre p08p25 - - - 12E - - - p08p47 12E - - - - - - p11 10F - 9E 9F 12A - - # p11 nu are ferestre p11p44 - 11E - - - - - p25 8G 10B 11A - - - - p07 - - 12F 9B 10C 10A - # p07 nu are ferestre p34p07 - - - - - - 10E # p34 şi p07 p09 - - 7G 10D 11F 11C - # p09 nu are ferestre p34p09 - 9E - - - - - # p34 şi p09 # restul profesorilor (majoritatea) nu intră în cuplaje
Am redat aici numai liniile profesorilor care fac parte din cuplaje; am verificat că pe celelalte linii, avem în total 5 ferestre – deci a 6-a fereastră ar trebui să fie pe una dintre liniile redate… ceea ce nu este adevărat: p34
nu are ore proprii în ziua respectivă şi face a 2-a oră împreună cu p09
, apoi tocmai a 7-a oră, împreună cu p07
– însemnând că are 4 ferestre (şi numai de aici, ar putea rezulta „a 6-a fereastră” – ceilalţi profesori cuplaţi nu au vreo fereastră). Rezultatul corect era 5+4=9 ferestre, nu 6.
2.
Pentru un alt orar, imediat după lansarea programului s-a semnalat:
Error in if (bitwAnd(bith[prf], B) > 0) return(FALSE) : missing value where TRUE/FALSE needed
Comanda imediată traceback()
ne-a indicat sursa erorii:
# dintr-o versiune anterioară a programului: vrf_over <- function(morr) { bith <- get_bin_patt(morr) # şabloanele binare ale orelor alocate curent for(prf in nx2) { # 'prf' este unul dintre profesorii fictivi B <- 0L for(pc in Lx2[[prf]]) B <- bitwOr(B, bith[pc]) if(bitwAnd(bith[prf], B) > 0) # cam de aici, provine eroarea return(FALSE) } TRUE # lecţiile cuplate de profesorii fictivi NU se suprapun }
N-a fost greu de lămurit lucrurile: întâi, am afişat 'bith
' şi am verificat că acest vector conţine într-adevăr, numai profesorii care au ore în ziua respectivă (indicând pe câte un octet, orele 1..7 alocate fiecăruia prin orarul zilei); apoi, am listat 'Lx2
', care este în fond un „dicţionar” prin care fiecărui profesor fictiv existent în orar i se asociază câte un vector care conţine profesorii (fictivi sau nu) cu care acesta nu are voie să se suprapună într-o aceeaşi oră – şi am descoperit îndată că acest dicţionar conţine şi "p08p11
", care în ziua respectivă nu are ore (şi deci trebuia eliminat din Lx2
).
Când 'prf
' ajunge la "p08p11
", vectorul Lx2[[prf]]
devine "(p08, p11, p08p25, p08p47, p11p44)
" – dar "p08p25
" nu apare nici el în ziua respectivă, încât atunci când 'pc
' ajunge "p08p25
", valoarea bith[pc]
este nedefinită ("missing value", sau NA
), iar NA
se propagă mai departe, conducând la mesajul de eroare deja redat mai sus.
Prin urmare, greşala trebuie căutată în funcţia anterioară select_cupl()
, a cărei intenţie era aceea de a restrânge listele săptămânale Lx1
şi Lx2
la datele existente în ziua curentă ("p08p11
" trebuia eliminat din Lx2
).
Dar este de observat o altă greşală, de data aceasta una „promiţătoare”. Înfiinţasem vrf_over()
pentru a semnala vreo eventuală suprapunere ascunsă, apărută în urma mutării prin move_cls()
a unei clase, dintr-o coloană orară în alta; numai că mutarea de clase între două coloane angajează numai aceste două coloane ale orarului, în timp ce vrf_over()
verifică absenţa suprapunerilor pe toate coloanele. Progresăm deci, la ideea de a elimina vfr_over()
, incluzând în schimb la finalul lui move_cls()
o mică secvenţă de verificare a celor două coloane.
3.
În sfârşit, urmărind iarăşi un anumit progres, să analizăm cumva funcţia anterioară choose_min()
; ea preia orarul rezultat după o mutare de clasă, obţine din cover_gaps() lista reparaţiilor de ferestre, aplică prin move_cls() fiecare dintre aceste reparaţii şi înregistrează numărul de ferestre rezultat astfel; în final returnează primul orar dintre cele „reparate”, care are numărul minim de ferestre:
# choose_min() din prima versiune a programului choose_min1 <- function(mxt) { # 'mxt': orarul rezultat după mutarea unei clase swp <- cover_gaps(mxt) # lista reparaţiilor „standard” de ferestre swp$ng <- 100 for(i in 1:nrow(swp)) { # aplică pe rând, reparaţiile mor <- move_cls(mxt, swp[i,1], swp[i,2], swp[i,3]) if(! is.null(mor)) { swp$ng[i] <- count_gaps(mor) # numărul de ferestre (inclusiv, fictive) } } im <- which.min(swp$ng) # reţine primul orar cu minimum de ferestre list(move_cls(mxt, swp[im,1], swp[im,2], swp[im,3]), swp$ng[im]) }
Am adăugat în tabelul de reparaţii coloana $ng
, pe care am înscris (prin for()
) pentru fiecare nou orar numărul de ferestre ale acestuia; în final, am determinat valoarea minimă din coloana $ng
şi pentru a returna rezultatul, am re-aplicat move_cls()
pe datele liniei respective.
Dar de regulă este mai eficient să folosim operaţii map()
în loc de for()
şi pare mai eficient să folosim un vector ng
simplu, în loc de a anexa tabelului şi a folosi coloana $ng
(deşi… o asemenea coloană nu este decât tot un „vector simplu”):
# variantă de choose_min(), cu map_dbl() choose_min2 <- function(mxt) { # 'mxt': orarul rezultat după mutarea unei clase swp <- cover_gaps(mxt) # lista reparaţiilor „standard” de ferestre ng <- map_dbl(1:nrow(swp), function(i) { mor <- move_cls(mxt, swp[i,1], swp[i,2], swp[i,3]) ifelse(is.null(mor), 100, count_gaps(mor)) }) im <- which.min(ng) # reţine primul orar cu minimum de ferestre list(move_cls(mxt, swp[im,1], swp[im,2], swp[im,3]), ng[im]) }
Dar decât să reţinem într-un vector toate numerele de ferestre, să determinăm numărul minim înscris în acest vector şi apoi să re-aplicăm move_cls()
pe linia de reparaţii corespunzătoare acestui minim – parcă ar trebui să fie mai eficient, algoritmul obişnuit de găsire a minimului dintr-o secvenţă introdusă iterativ (păstrăm la fiecare pas valoarea mai mică):
# variantă de choose_min() (căutarea obişnuită, a minimului) choose_min3 <- function(mxt) { # 'mxt': orarul rezultat după mutarea unei clase ng <- count_gaps(mxt) swp <- cover_gaps(mxt) # lista reparaţiilor „standard” de ferestre M <- mxt for(i in 1:nrow(swp)) { mor <- move_cls(mxt, swp[i,1], swp[i,2], swp[i,3]) if(! is.null(mor)) { ng1 <- count_gaps(mor) if(ng1 < ng) { ng <- ng1 M <- mor # la momentul curent, are minimum de ferestre } } } list(M, ng) }
Următoarea secvenţă ad-hoc iterează pe lista constituită din funcţiile redate mai sus şi măsoară pentru fiecare, timpul de execuţie pe câte 1000 de repetări:
for(choose in list(choose_min1, choose_min2, choose_min3)) { start <- Sys.time() for(i in 1:1000) LM <- choose(MXT) end <- Sys.time() print(end - start) } Time difference of 3.827449 mins choose_min1() Time difference of 3.819324 mins choose_min2() Time difference of 3.844236 mins choose_min3()
Ca timp de execuţie, diferenţa între ele este foarte mică – totuşi, ea se menţine pe toate orarele încercate şi fiindcă vom avea de repetat choose_min()
de măcar vreo două-trei mii de ori, vom alege de-acum a doua dintre cele trei funcţii (ceva mai rapidă, dar şi mai scurtă şi să zicem elegantă, faţă de celelalte).
vezi Cărţile mele (de programare)