„Tabelul de încadrare” (TIP
) specifică pentru fiecare profesor al şcolii, clasele care i-au fost alocate şi numărul de ore pe săptămână la fiecare dintre acestea (v. [1]). O primă problemă care s-ar pune constă în repartizarea orelor din TIP
pe zilele de lucru ale săptămânii; dacă excludem preferinţele individuale, rămân două condiţii: să nu se depăşească un anumit număr de ore pe zi, la fiecare clasă şi respectiv, orele fiecărui profesor să fie repartizate cât se poate de omogen (prima este obligatorie, a doua ar fi rezonabilă).
De regulă, numărul de ore pe zi ale fiecărei clase este cuprins între 5 şi 7; dacă o clasă are 30 de ore pe săptămână, atunci în fiecare zi vor trebui puse câte 6 ore. Dacă un profesor are 20 de ore pe săptămână, atunci ar trebui sa aibă câte 4 ore pe zi (deşi poate că preferă să aibă 2 ore într-o zi şi 6 într-o alta).
De obicei, şcolile noastre funcţionează în două schimburi, clasele venind în prima parte şi respectiv, în a doua parte a zilei, în sălile de clasă existente. Ar apărea atunci, o a treia condiţie „rezonabilă”: orele profesorilor care au clase în ambele schimburi să fie repartizate pe zile astfel încât să se evite (pe cât se poate) situaţia în care profesorul respectiv trebuie să vină într-o aceeaşi zi şi în primul schimb şi în cel de-al doilea. Deocamdată, aici vom ignora această a treia condiţie – încât putem să considerăm un singur schimb (fie lucrând ca şi cum toate clasele ar fi într-un acelaşi schimb, fie – mai bine – lucrând numai cu clasele din primul schimb, urmând să repetăm apoi, pentru clasele rămase).
În [2] am expus (printr-un program Perl) o metodă de repartizare echilibrată pe zilele de lucru, a orelor din TIP
. Se constituia întâi un tablou de ”şabloane de repartizare” a orelor unui profesor dat la o clasă oarecare; de exemplu, 4 ore ar putea fi plasate în 5 moduri, după cum omitem una dintre cele 5 zile de lucru (dacă omitem două zile, atunci avem o repartizare ne-echilibrată: clasa ar avea două dintre cele 4 ore într-o aceeaşi zi).
Se parcurg pe rând liniile din TIP
, repartizând orele profesorului curent conform şabloanelor asociate; se va ajunge în impas atunci când aplicarea unuia sau altuia dintre şabloanele posibile pentru cazul curent, ar conduce la depăşirea numărului de ore pe zi la clasa respectivă.
S-ar putea ieşi eventual, din acest impas, urmând mecanismul backtracking (se revine la linia anterioară pentru a înlocui şablonul care i-a fost aplicat şi a relua apoi lucrurile pentru linia la care nu se reuşise repartizarea). Dar programul menţionat nu procedează astfel (timpul de execuţie ar fi exorbitant), ci speculează acest postulat: există o ordine a liniilor din TIP
pentru care repartizarea decurge fără vreun impas!
După fiecare impas, lucrul curent este abandonat; se reordonează aleatoriu liniile din TIP
şi se ia repartizarea de la capăt.
În practica obişnuită (concretizând TIP
, din diverse şcoli) am constatat că după un număr care poate ajunge la câteva zeci de mii de astfel de reluări (puţin, la puterea de calcul actuală), se întâlneşte şi acea ordine de parcurgere care decurge liniar şi complet (fără impas), rezultând una dintre repartizările posibile ale TIP
.
Metoda descrisă succint mai sus poate fi clasată între metodele de tip Monte Carlo (v. wikipedia.org
); desigur, nu ştiam despre asta (şi de atâtea altele) prin anii 2000 (când învăţam Perl ocupându-mă de programul menţionat), ba chiar un timp, am crezut că am inventat o metodă nouă… Însă acum aş zice că o asemenea metodă de repartizare ţine de metodele de „a da cu tunul în vrăbii”; repartizarea pe zile a TIP
se poate demara mult mai simplu, iar dacă omitem a doua condiţie, se poate obţine imediat.
Ideea este de a pleca nu de la şabloanele orare ale profesorilor (ceea ce obligă la multiple reveniri, pentru a păstra numărul de ore pe zi la clase), ci de la condiţia obligatorie asupra orelor pe zi ale claselor.
Lucrurile pot fi modelate în fel şi chip, în diverse limbaje; dar problemele ridicate în contextul unui „set de date” existent, cum este şi TIP
, pot fi abordate în mod natural prin limbajul R – recunoscut pentru scurtimea şi expresivitatea formulărilor prin care se pot regiza diverse operaţii complexe cu seturi de date (chiar şi foarte mari, dar şi de talie modestă ca în cazul TIP
).
Pentru a evidenţia ideea repartizării directe a TIP
pe zilele de lucru, să presupunem întâi că am avea o singură clasă – să zicem, cu acest „plan-cadru de învăţământ” (se pot găsi pe site-ul Ministerului Învăţământului – desigur ca fişiere ".doc
"):
obj, ore rom, 4 eng, 2 fra, 2 mat, 4 fiz, 3 chi, 2 bio, 2 ist, 1 geo, 1 sum, 1 rel, 1 muz, 1 des, 1 edf, 1 tic, 2 inf, 2
Aici am redat un „plan-cadru” ca fişier CSV – să-l numim plan-cadru.csv
– şi ne-am permis să abreviem cu câte trei litere denumirile obiectelor de învăţământ specificate pentru clasa respectivă (şi am omis catalogarea de „arii curriculare”).
Ideea cea mai simplă pentru repartizarea omogenă pe zilele de lucru a orelor existente constă în următoarele operaţii succesive:
– se ordonează liniile, descrescător după valorile din coloana ore
;
– se multiplică liniile pe care ore
este mai mare decât 1 (descrescând ore
);
– se alipeşte o coloană pe care se repetă de sus în jos, secvenţa 1, 2, 3, 4, 5 (acestea fiind numerele de ordine ale zilelor de lucru).
Cele 4 ore de rom
vor fi plasate în zilele 1, 2, 3 şi 4; cele 4 ore de mat
vor fi în zilele 5, 1, 2 şi 3; cele 3 ore de fiz
– în zilele 4, 5 şi 1; ş.a.m.d.
Să observăm că algoritmul descris respectă în mod implicit, condiţia ca fiecare clasă să aibă cel mult 7 ore pe zi: secvenţa 1..5 va fi repetată de exact (n div 5
) ori, n
fiind numărul de ore pe săptămână; dacă n
este cuprins între 25 şi 30, atunci fiecare zi 1..5 va apărea fie de 5 ori, fie de 6 ori (în unele zile vor fi 5 ore, în altele 6).
Desigur, fiind vorba de o singură clasă, algoritmul descris (fiind şi foarte simplu) este uşor de implementat în orice limbaj; în R l-am formula astfel:
library(tidyverse) plan <- read_csv("plan-cadru.csv", col_types="ci") zile <- gl(n=5, k=1, length = sum(plan$ore), # atâtea valori câte ore are în total, clasa ordered=TRUE ) # "factor" ordonat, având ca nivele zilele de lucru orar <- plan[order(-plan$ore), ] %>% # ordonează descrescător după $ore uncount(ore) %>% # multiplică liniile de câte $ore ori fiecare mutate(zl = zile) %>% # adaugă coloana ("factor") $zl split(., .$zl) %>% # separă liniile după valoarea $zl map_dfc(., function(Z) as.data.frame(Z$obj, optional=TRUE) ) # produce un tabel având drept coloane cele 5 grupuri date de $zl print(orar)
Prin gl()
am introdus zile
ca referinţă la un obiect de clasă "factor", având ca nivele cele 5 zile de lucru, ordonate după rang; cele 5 valori au fost repetate succesiv până ce lungimea vectorului devine egală cu numărul total de ore ale clasei.
uncount(ore)
multiplică acele linii din obiectul "data.frame" curent (transmis din stânga operatorului "%>%
"), pe care valoarea din coloana ore
este mai mare ca 1.
map_dfc()
combină într-un data.frame final, coloanele $obj
asociate fiecare (după split()
) câte unei zile – rezultând acest (posibil) orar al clasei:
...1 ...2 ...3 ...4 ...5 1 rom rom rom rom mat 2 mat mat mat fiz fiz 3 fiz eng eng fra fra 4 chi chi bio bio tic 5 tic inf inf ist geo 6 sum rel muz des edf
Desigur, considerând mai multe clase este de aşteptat ca repartizarea obţinută aplicând procedeul de mai sus să nu mai fie „omogenă” (încălcând condiţia ca fiecare profesor să aibă cam acelaşi număr de ore în fiecare zi). Este de observat că algoritmul de mai sus asigură că orele la o aceeaşi clasă ale unui profesor oarecare vor fi repartizate în zile diferite (exceptând cazul când numărul acestora este mai mare ca 5).
TIP
Procedeul exemplificat mai sus pentru o singură clasă respectă în mod implicit, condiţiile unei repartizări omogene; aceste condiţii vor fi respectate (implicit) şi în cazul mai multor clase, dar cu anumite abateri (de tratat cumva, ulterior). Să nu ne mai gândim deocamdată, la condiţii; să vedem cum am reformula procedeul de mai sus pentru cazul claselor dintr-un acelaşi schimb (şi să vedem ce obţinem).
Pentru experimentări ne trebuie un „tabel de încadrare”; putem avea un TIP
în diverse formate, dar cel mai convenabil în vederea prelucrării datelor respective este „formatul lung”, cu 3 coloane: Obiect, Profesor, Clasa.
Din [1] putem extrage un TIP
, salvându-l ca obiect "tibble" în fişierul TIP.rds
:
> TIP # A tibble: 828 x 3 obj prof clasa <chr> <chr> <chr> 1 rom P05 12C 2 fra P07 11E 3 eng P10 12E 4 eng P11 11B 5 eng P12 11D 6 mat P16 12D 7 mat P18 11G 8 fiz P20 12A 9 ist P26 11C 10 sum P28 11A # … with 818 more rows
În acest format de TIP
, fiecare triplet (obiect, profesor, clasa) apare pe un număr de linii egal cu numărul de ore la clasa respectivă ale profesorului, pe obiectul respectiv (încât nu va mai fi nevoie să folosim uncount()
, ca în programul de mai sus):
> with(TIP, TIP[prof=="P05" & clasa=="12C", ]) # A tibble: 5 x 3 obj prof clasa <chr> <chr> <chr> 1 rom P05 12C 2 rom P05 12C 3 rom P05 12C 4 rom P05 12C 5 rom P05 12C # cele 5 ore de "rom" la clasa "12C" ale profesorului "P05"
În următorul program R, recuperăm obiectul tibble din fişierul TIP.rds
, investigăm care sunt clasele şi convenim care sunt într-un schimb şi care în celălalt.
Introducem apoi funcţia set_zile(Q)
, unde argumentul 'Q
' referă (sau chiar este) un obiect tibble care conţine toate liniile din TIP
corespunzătoare unei aceleiaşi clase; liniile respective sunt ordonate descrescător după numărul de linii identice (deci, după numărul de ore ale profesorilor la clasa respectivă) – folosind sort(table())
şi transformând coloana $prof
în "factor"; apoi se adaugă coloana de tip factor ordonat $zl
, asociind astfel câte o zi fiecărei ore; în final set_zile()
returnează obiectul 'Q
' astfel modificat (anume, ca un obiect data.frame):
# distribute.R library(tidyverse) TIP <- readRDS("TIP.rds") # un "tabel de încadrare" (în format lung) # în TIP sunt 28 de clase, câte 7 pe fiecare nivel Cls <- sort(unique(TIP$clasa))[c(22:28, 1:21)] # Clasele, după nivel şi literă clPM <- Cls[1:14] # clasele din schimbul 2 (nivelele 9 şi 10) clAM <- Cls[15:28] # clasele din schimbul 1 (11 şi 12) zile <- c("lu", "ma", "mi", "jo", "vi") # zilele de lucru (în ordinea ştiută) # returnează un tabel de alocare a zilelor pentru orele unei clase set_zile <- function(Q) { # 'Q' conţine liniile din TIP cu o aceeaşi 'clasa' srt <- sort(table(Q$prof), decreasing=TRUE) Q$prof <- factor(Q$prof, levels=names(srt)) Q <- arrange(Q, prof) # ordonează liniile descrescător după numărul de ore Q$zl <- gl(n=5, k=1, length = nrow(Q), ordered=TRUE, labels = zile) # alocă zile pentru ore as.data.frame(Q) } # distribuie pe zile orele claselor indicate în vectorul 'vCl' distr <- function(vCl) { TIP %>% filter(clasa %in% vCl) %>% split(., .$clasa) %>% map_df(., function(K) set_zile(K)) }
Funcţia „principală” distr()
primeşte ca argument o listă de clase – clAM
, sau clPM
, sau Cls
, sau un vector c(...)
conţinând una sau mai multe clase oarecare – şi extrage din TIP
liniile aferente acestor clase, apoi prin map_fd()
invocă set_zile()
pentru fiecare subgrup de linii aferente unei aceleiaşi clase, reunind într-un obiect data.frame (returnat în final) obiectele data.frame produse de set_zile()
.
Este uşor să sintetizăm diverse aspecte de omogenitate ale repartizării obţinute prin programul de mai sus, aplicând funcţii ca table()
şi addmargins()
.
vb@Home:~/21ian$ R -q > source("distribute.R") > PM <- distr(clPM) # distribuţia pe zile a orelor din schimbul 2 > table(PM[c('zl', 'clasa')]) # tabelul de contingenţă între zile şi clase clasa zl 10A 10B 10C 10D 10E 10F 10G 9A 9B 9C 9D 9E 9F 9G lu 7 6 7 6 7 6 6 7 6 6 6 6 6 6 ma 6 6 6 6 6 6 6 6 6 6 6 6 6 6 mi 6 6 6 6 6 6 6 6 6 6 6 6 6 6 jo 6 6 6 6 6 6 6 6 6 5 6 6 6 6 vi 6 6 6 6 6 5 6 6 5 5 6 6 6 6
Tabelul rezultat confirmă observaţia făcută anterior, asupra faptului că algoritmul folosit asigură în mod implicit repartizarea uniformă a orelor fiecărei clase, pe zile (clasele cu 30 de ore – precum 9G
– au câte 6 ore pe zi; cele cu 28 de ore – precum 9E
– au 3 zile cu câte 6 ore şi 2 zile cu câte 5 ore; ş.a.m.d.).
Desigur, toate clasele cu 31 de ore (10A
, 10C
, 10E
şi 9A
) au 7 ore într-o aceeaşi zi, anume în prima – pentru că 6 repetări ale secvenţei 1..5 acoperă 30 de ore, deci a 31-a oră va fi alocată zilei 1; analog, clasele cu mai puţin de 30 de ore vor avea mai puţin de 6 ore în ultimele zile.
Dar situaţia în care ar fi multe clase cu 7 ore într-o aceeaşi zi trebuie evitată, fiindcă ar genera conflicte între schimburi (acestea partajând aceleaşi săli de clasă); următoarea funcţie (de adăugat în fişierul distribute.R
) permite schimbarea între ele a listelor de ore din două zile, pentru clasa al cărei tabel de ore s-a indicat:
# schimbă orele dintr-o zi cu orele dintr-o alta (pe tabelul orelor unei clase) change_days <- function(dfQ, z1, z2) { Z <- dfQ$zl # coloana alocării orelor pe zile if(is.numeric(z1)) {z1 <- zile[z1]; z2 <- zile[z2]} for(i in 1:length(Z)) { if(Z[i] == z1) Z[i] <- z2 # orele din ziua 'z1' trec în ziua 'z2' else if(Z[i] == z2) Z[i] <- z1 # orele din ziua 'z2' trec în ziua 'z1' } dfQ$zl <- Z dfQ # returnează tabelul modificat, al orelor clasei }
Pentru argumentele 'z1
' şi 'z2
' se pot pasa fie indici, fie valori din vectorul zile
; situaţia când s-ar amesteca un indice şi o valoare (printr-un apel ca change_days(Q, 1, "mi")
) este ne-firească, încât am testat numai 'z1
' (să evităm chiţibuşeriile!).
Însă dacă este vorba de a schimba între ele două anumite zile, pentru mai multe clase – ale căror tabele de ore sunt conţinute într-o aceeaşi „bază de date”, precum PM
obţinut mai sus – atunci change_days()
este neconvenabil de folosit: ar trebui să extragem (din PM
) datele clasei curente, să apelăm pentru acestea change_days()
şi să reinserăm în PM
rezultatul, repetând pentru fiecare clasă (astfel, se vor produce în mod inerent, multiple copieri interne de obiecte data.frame). Este preferabil să rescriem funcţia de mai sus astfel:
# glob_ch_days(PM, c("10A", "10C", "10E"), c(1,1,1), c(2,3,4)) -> PM1 glob_ch_days <- function(dfG, vCl, vZ1, vZ2) { Z <- dfG$zl # coloana alocării orelor pe zile, pentru toate clasele for(j in 1:length(vCl)) { q <- vCl[j] # clasa ale cărei zile trebuie schimbate z1 <- vZ1[j] # zilele între care se vor schimba orele z2 <- vZ2[j] if(is.numeric(z1)) {z1 <- zile[z1]; z2 <- zile[z2]} for(i in 1:length(Z)) { if(dfG[i, 3] == q) { # 'clasa' este a 3-a coloană, în 'dfg' if(Z[i] == z1) Z[i] <- z2 else if(Z[i] == z2) Z[i] <- z1 } } } dfG$zl <- Z dfG }
Chiar să schimbăm în PM
, prima zi cu a 2-a, a 3-a şi a 4-a, la clasele 10ACE
(dar putem şi amâna această schimbare, văzând întâi repartizarea obţinută):
> PM <- glob_ch_days(PM, c("10A", "10C", "10E"), c(1,1,1), c(2,3,4))
Să vedem acum, pentru PM
, cum au fost distribuite pe zile orele profesorilor:
> (hPM <- addmargins(table(PM[c('prof', 'zl')])))
zl zl prof lu ma mi jo vi Sum prof lu ma mi jo vi Sum P15 3 1 1 3 2 10 P19 0 1 1 0 0 2 P44 2 3 2 0 2 9 P22 1 4 6 6 2 19 P06 1 4 4 4 2 15 P31 1 1 2 1 1 6 P20 4 0 2 5 4 15 P43 2 0 1 0 0 3 P01 1 3 2 0 2 8 P50 0 0 3 7 2 12 P09 2 0 1 1 2 6 P37 3 3 2 1 1 10 P11 5 4 1 2 3 15 P33 2 2 3 2 2 11 P24 3 3 3 3 1 13 P18 3 2 1 1 3 10 P53 0 1 2 3 3 9 P13 1 1 3 1 0 6 P26 1 0 0 1 2 4 P21 3 1 1 4 7 16 P29 2 4 0 1 0 7 P02 3 3 3 1 0 10 P30 2 3 2 0 2 9 P35 0 2 2 2 2 8 P32 0 1 5 1 0 7 P41 2 2 1 2 2 9 P48 2 1 2 2 0 7 P52 1 0 1 3 4 9 P49 0 2 0 2 2 6 P54 1 1 1 1 0 4 P51 0 1 0 0 1 2 P38 3 1 1 2 2 9 P16 3 2 2 3 3 13 P40 3 3 2 1 2 11 P03 3 3 3 2 0 11 P05 2 1 1 1 1 6 P07 0 2 1 1 1 5 P39 2 2 1 1 2 8 P12 1 1 1 2 1 6 P28 3 4 2 0 0 9 P27 3 1 2 0 1 7 P45 0 0 0 0 1 1 P42 1 1 0 0 0 2 P23 1 0 0 0 0 1 P04 2 3 1 1 1 8 P14 0 1 1 0 0 2 P08 1 1 3 5 4 14 P36 2 1 1 1 1 6 P25 2 1 1 0 4 8 P34 1 1 1 0 0 3 P17 1 2 3 4 3 13 Sum 85 85 85 84 81 420
Constatăm întâi că repartizarea s-a obţinut imediat şi este completă (s-au distribuit toate cele 420 de ore din schimbul al doilea). Dar în puţine cazuri, orele au fost plasate omogen (cu diferenţe de cel mult o oră, de la o zi la alta); am evidenţiat în tabloul redat mai sus câteva cazuri – dar teoretic, sunt multe – de abatere nedorită (de exemplu, profesorul P21
, cu Sum
=16 ore, are vineri 7 ore, iar marţi şi miercuri câte o singură oră).
Desigur, trebuie să ţinem seama de faptul că bună parte dintre profesori au ore şi în primul schimb – încât aprecierea omogenităţii distribuţiei de mai sus trebuie făcută după ce, aplicând iarăşi distr()
, obţinem şi repartiţia orelor din celălalt schimb; bineînţeles că era şi mai bine să fi folosit distr(Cls)
, obţinând (imediat) repartiţia pe zile a tuturor orelor (nu numai a schimbului PM
, ca mai sus).
Nu-i uşor de administrat vreo procedură generală, pentru amendarea distribuţiei.
Ce ar fi de făcut pentru a corecta de exemplu, distribuţia (3 1 1 4 7
) obţinută mai sus pentru P21
?
Ca să rezulte ceva omogen, ca (3 3 3 4 3
), ar trebui mutate 4 ore dintre cele 7 de "vi
", plasându-le câte două în zilele "ma
" şi "mi
"; în primul rând, trebuie să avem grijă ca acele 2 ore adăugate astfel în cele două zile, să fie la clase diferite de clasa asociată orei deja existente (altfel, P21
ar avea într-o aceeaşi zi, două ore la o aceeaşi clasă – ceea ce în general este de evitat); apoi (pentru a nu perturba la clase, numărul de ore pe zi), avem de ales dintre ceilalţi profesori cu ore "ma
" şi respectiv "mi
" la cele 4 clase mutate, pentru a muta înnapoi în ziua "vi
" câte două ore dintre acestea.
Deocamdată nu ne propunem mai mult decât să amendăm în mod interactiv, o distribuţie ca PM
– imaginând în acest scop câteva „funcţii ajutătoare” (în care PM
este „variabilă globală”, referind distribuţia de retuşat, deja existentă).
hPM
obţinut mai sus prin table()
şi addmargins()
este un tablou numeric, în care cele 52 de linii sunt notate cu nivelele factorului PM$prof
, iar coloanele sunt notate prin nivelele factorului PM$zl
(adăugând câte un nou nivel, pentru "Sum
"); folosind hPM
în această formă, va fi incomod să obţinem de exemplu, lista profesorilor care au un anumit număr de ore într-o anumită zi (şi nu este simplu nici de re-ordonat liniile).
Mai mult, putem avea nevoie de "hPM
" în diverse momente de pe parcursul retuşării interactive a lui PM
; următoarea funcţie produce un "hPM
" ca obiect data.frame, cu liniile ordonate descrescător după numărul total de ore ale profesorului:
hoursPM <- function() { addmargins( table(PM[c('prof', 'zl')]), # tabel de contingenţă nivele $prof, $zl 2 # interesează numai suma orelor profesorului (nu şi sumele pe coloane) ) %>% # a rezultat: 'table' num [1:51, 1:6] as.data.frame(.) %>% # $prof, $zl, $Freq (51 prof * 5 zile + 51_Sum*5 = 306 linii) spread(., zl, Freq) %>% # $prof, $lu, ..., $vi, $Sum .[order(-.$Sum), ] # descrescător după numărul de ore ale profesorului } > hPM <- hoursPM() > head(hPM) prof lu ma mi jo vi Sum 28 P22 1 4 6 6 2 19 36 P21 3 1 1 4 7 16 3 P06 1 4 4 4 2 15 4 P20 4 0 2 5 4 15 7 P11 5 4 1 2 3 15 24 P08 1 1 3 5 4 14
Acum va fi comod de selectat profesori (după unul sau mai multe criterii); de exemplu, hPM[hPM$vi == 0, 1]
ne dă lista profesorilor – aceştia figurează în coloana indicată, 1 – care au 0
(zero) ore în ziua "vi
".
Pentru o repartizare omogenă, numărul de ore pe zi ale profesorului ar trebui să fie în jurul valorii $Sum/5
(de exemplu, "P22
" şi "P21
" ar fi trebuit să aibă zilnic, 3 sau 4 ore). Ar fi de adăugat în hPM
o coloană $omg
care să reflecte cumva „gradul de omogenitate”; de exemplu, am putea considera că repartizarea de pe linia respectivă este suficient de „omogenă”, dacă diferenţa valorilor extreme este cel mult 2:
hPM$omg <- apply(hPM[, 2:6], 1, function(x) max(x) - min(x))
Următoarea funcţie depistează în hPM
curent profesorii (din coloana 1) care au într-o anumită zi un anumit număr de ore (şi returnează lista acestora):
profNoreZ <- function(N, Z) { # profesorii cu N ore în ziua Z hPM[hPM[[Z]] == N, 1] } # Z este "lu".."vi" (sau 2..6, fiindcă în hPM, coloanele zilelor sunt 2..6) > profNoreZ(7, "vi") [1] P21 51 Levels: P15 P44 P06 P20 P01 P09 P11 P24 P53 P26 P29 P30 P32 P48 P49 ... P34 > profNoreZ(0, "vi") [1] P03 P02 P28 P29 P32 P48 P13 P54 P43 P34 P42 P19 P14 P23
Următoarea funcţie furnizează lista claselor alocate în PM
unui anumit profesor, într-o anumită zi:
claseProfZ <- function(pr, Z) { # clasele alocate profesorului în ziua Z if(is.numeric(Z)) Z <- zile[Z] PM[PM$prof == pr & PM$zl == Z, "clasa"] } > claseProfZ("P21", "vi") [1] "10D" "10E" "10F" "10G" "9C" "9D" "9E"
Poate fi util un tabel care să indice pentru toţi profesorii (nu pentru unul singur, cum face claseProfZ()
), clasele repartizate acestora într-o zi sau alta; în acest scop, putem „vectoriza” – prin lapply()
– funcţia de mai sus, astfel:
profZiCls <- function() { pzc <- hPM; pzc$Sum <- NULL for(Z in zile) { pzc[[Z]] <- lapply(pzc$prof, claseProfZi, Z) } pzc }
De exemplu, să vedem clasele alocate profesorilor în două anumite zile:
> PZC <- profZiCls() > select(PZC, prof, "ma", "vi") prof ma vi 28 P22 10D, 10G, 9D, 9E 9C, 9F 36 P21 9G 10D, 10E, 10F, 10G, 9C, 9D, 9E 3 P06 10A, 10D, 9C, 9F 10A, 9C 4 P20 10B, 9A, 9B, 9F 7 P11 9A, 9B, 9D, 9F 10A, 10C, 9G # etc. (am redat doar primele câteva linii)
Funcţia următoare asigură mutarea în PM
curent, a orei unui profesor la o clasă precizată, dintr-o zi în alta:
moveProfClZZ <- function(pr, cl, oldZ, newZ) { if(is.numeric(oldZ)) {oldZ <- zile[oldZ]; newZ <- zile[newZ]} PM[with(PM, prof==pr & clasa==cl & zl==oldZ), "zl"] <<- newZ }
Precizăm că "<<-
" este operatorul de „asignare globală” (pentru a modifica un obiect dinafara contextului prevăzut funcţiei).
După ce vom folosi moveProfClZZ()
, va trebui să actualizăm "hPM
" (prin hoursPM()
), fiindcă profNoreZ()
(ba şi profZiCls()
) îşi produce rezultatele folosind hPM
curent.
Putem considera şi alte funcţii asemănătoare celor de mai sus; de exemplu, pentru a testa dacă un profesor are sau nu oră într-o anumită zi, la o anumită clasă:
testPrClZ <- function(pr, cl, Z) { nrow(PM[with(PM, prof==pr & clasa==cl & zl==Z), ]) > 0 }
Desigur, ar fi de gândit o procedură care să înlănţuie cumva funcţiile constituite mai sus, asigurând (automat) unele tipuri de îndreptări, de exemplu „îndreptarea” cazurilor de profesori cărora li s-a alocat în PM
mai mult de 6 ore într-o zi sau alta (sau, pentru alt exemplu, „îndreptarea” cazurilor în care hPM$omg
este mai mare decât 3).
vezi Cărţile mele (de programare)