momente şi schiţe de informatică şi matematică
anti point—and—click

Repartizarea pe zile a încadrării profesorilor

R | orar şcolar
2020 dec

„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).

Repartizarea cu şabloane orare (metodă „Monte Carlo”)

Î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.

O metodă de repartizare directă

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).

Repartizarea pe zile a orelor unei clase

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).

Repartizarea directă, necondiţionată, a 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().

Analiza distribuţiei obţinute

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).

Funcţii ajutătoare, pentru retuşarea distribuţiei orelor

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)

docerpro | Prev | Next