În [1] am transformat documentul PDF care prezenta orarul unei şcoli (unul bine făcut), într-un obiect R de clasă "tibble"; redăm primele rânduri dintre cele 300 şi o parte dintre cele 14 coloane (primele cinci şi ultimele două):
> (orar <- readRDS("orar.rds")) # A tibble: 300 x 14 prof zl `1` `2` `3` ... `11` `12` 1 P01 Lu NA NA NA ... NA NA 2 P01 Ma NA NA NA ... NA NA 3 P01 Mi NA NA NA ... NA NA 4 P01 Jo NA NA NA ... "Rom\r\n9.C" NA 5 P01 Vi "Rom\r\n11.D" "Rom\r\n11.D" NA ... NA NA 6 P02 Lu NA "Rom\r\n11.B" "Rom\r\n12.C" ... NA NA 7 P02 Ma NA NA NA ... NA NA 8 P02 Mi NA NA NA ... NA NA 9 P02 Jo "Rom\r\n11.B" "Rom\r\n12.E" "Rom\r\n12.E" ... NA NA 10 P02 Vi NA NA NA ... NA NA
Tabelul conţine 300×14=4200 de valori; valorile diferite de NA
, din coloanele orelor `1`
..`12`
, reprezintă disciplina (precum "Rom") împreună cu clasa (precum "11.D", sau "9.C") la care profesorul indicat în coloana prof
trebuie să intre în ora respectivă a zilei prevăzute în coloana zl
.
Acest tabel reflectă vizualizarea obişnuită a orarului; dacă ştergem NA
, recunoaştem paginile din documentul PDF original (cu diferenţe nesemnificative: numele profesorului a fost „anonimizat” şi apare pe prima coloană a tabelului, în loc să fie în titlul subtabelului asociat profesorului respectiv; bineînţeles, lipsesc liniile orizontale şi verticale).
Dar ca „set de date”, acest tabel este defectuos; este inutil în general, să înregistrăm valori neexistente (reprezentate prin NA
); apoi, valorile înregistrate ar trebui să aibă fiecare propriul domeniu – ori aici, acestea ţin şi de obiectele de învăţământ şi de clasele existente (şi sunt exprimate în scopul afişării: când întâlneşte "Rom\r\n11.D
", vizualizatorul va afişa "Rom" în celula curentă, apoi interpretând caracterele de control "\r\n", va avansa în celula respectivă pe rândul următor şi va scrie "11.D").
Prin programul R următor „normalizăm” (sau explicităm) datele din tabelul 'orar
':
library(tidyverse) orar <- readRDS("orar.rds") orar <- orar %>% gather("ora", "obCls", 3:14) %>% separate(obCls, c("obj", "cls"), sep="\r\n") %>% filter(!is.na(cls)) %>% mutate(cls = sub(".", "", cls, fixed=TRUE))
Prin gather("ora", "obCls", 3:14)
s-au instituit două coloane (sau „variabile”): $ora
înregistrează repetat secvenţa 1:12 (indicând orele zilei), corespunzătoare numelor `1`
..`12`
ale coloanelor de ranguri 3:14 iniţiale; $obCls
înregistrează valorile din celulele aflate în tabelul iniţial pe coloanele 3:14. Apoi, separate()
constituie coloanele $obj
şi $cls
, pentru obiectele de învăţământ şi clasele care erau îmbinate iniţial prin "\r\n" în coloana $obCls
(păstrând însă valorile NA
). Apoi, filter()
reţine numai liniile pe care în coloana $cls
avem valori diferite de NA
. În final, mutate()
redefineşte coloana $cls
, eliminând (cu sub()
) caracterul ".
" care apărea în numele de clasă iniţiale.
Acum avem 5 variabile (cu domenii distincte între ele), cu valori grupate pe câte una dintre 925 de linii distincte două câte două:
> orar # inspectăm obiectul 'orar' (din consola R) şi îl salvăm # A tibble: 925 x 5 prof zl ora obj cls <chr> <chr> <chr> <chr> <chr> 1 P01 Vi 1 Rom 11D 2 P02 Jo 1 Rom 11B 3 P03 Jo 1 Rom 12D 4 P04 Mi 1 Rom 11A 5 P07 Ma 1 Rom 11E 6 P10 Lu 1 Eng 12D 7 P11 Lu 1 Eng 11C 8 P11 Ma 1 Eng 12F 9 P12 Mi 1 Fr 8 10 P13 Lu 1 Fr 12E # … with 915 more rows > saveRDS(orar, file="orarN.rds") # orarul „normalizat”
Cu alte cuvinte, avem în total 925 de ore, pentru profesorii, obiectele de învăţământ şi clasele existente – repartizate deja pe zilele săptămânii şi pe orele fiecărei zile.
De fapt, avem o excepţie – dintre cele 925 de ore, următoarele 11 ore au rămas fără alocare de zi (valoarea din $zl
fiind NA
):
> orar[is.na(orar$zl), ] # A tibble: 11 x 5 prof zl ora obj cls prof zl ora obj cls P49 NA 7 Ed.Muz 10C P49 NA 10 Ed.Muz 9D P50 NA 7 Ed.Muz 10F P49 NA 11 Ed.Muz 9B P49 NA 8 Ed.Muz 9C P49 NA 11 Ed.Muz 10A P50 NA 8 Ed.Muz 10E P49 NA 12 Ed.Muz 10D P50 NA 9 Ed.Muz 10G P49 NA 12 Ed.Muz 10B P49 NA 10 Ed.Muz 9A
Am observat deja în [1] că profesorii P49
şi P50
(cărora le aparţin aceste 11 ore „nealocate”) sunt cuplaţi cu profesorul P51
pe anumite clase. Să listăm şi orele lui P51
(cele dintr-o aceeaşi parte a zilei ca şi orele redate mai sus):
> orar[orar$prof=="P51" & as.integer(orar$ora) > 6, ] # A tibble: 16 x 5 prof zl ora obj cls prof zl ora obj cls P51 Lu 7 Ed.Viz 10F P51 Lu 11 Ed.Viz 5 P51 Vi 7 Ed.Viz 10C P51 Ma 11 Ed.Viz 6 P51 Lu 8 Ed.Viz 10E P51 Mi 11 Ed.Viz 9B P51 Vi 8 Ed.Viz 9C P51 Vi 11 Ed.Viz 10A P51 Lu 9 Ed.Viz 10G P51 Lu 12 Ed.Viz 9F P51 Lu 10 Ed.Viz 9E P51 Ma 12 Ed.Viz 9G P51 Mi 10 Ed.Viz 9A P51 Mi 12 Ed.Viz 10D P51 Vi 10 Ed.Viz 9D P51 Vi 12 Ed.Viz 10B
Cu siguranţă, între condiţiile iniţiale furnizate programului folosit pentru construirea orarului, figurează şi condiţia de separare a unor clase pe grupe de elevi, pentru anumite discipline şcolare şi pentru anumiţi profesori. Ştiind (sau în cazul nostru, deducând) aceste condiţii, putem interpreta cele două tabele redate mai sus; de exemplu, clasa 10C
este împărţită, în ziua Vi
(cum vedem în al doilea tabel), ora a 7-a, în două grupe: una face Ed.Muz
cu P49
, iar cealaltă face Ed.Viz
cu P51
(să observăm că dacă se punea "Vi
" şi în primul tabel, în loc de NA
, atunci rezulta o „suprapunere” de ore – ceea ce nu este permis într-un orar).
Noi intenţionăm (ca şi în [1]) să generăm o distribuţie pe zile a orelor (şi nu să o construim pe baza unor condiţii externe prealabile) – încât vom ignora jumătăţile de clasă (deci vom opera numai pe cele 914 ore pentru care valoarea $zl
din orarul iniţial nu este NA
); la sfârşit, prin anumite operaţii de retuşare a repartizării obţinute, vom putea lua în seamă şi diverse excepţii sau cazuri particulare (inclusiv, existenţa celor 11 jumătăţi de clasă ignorate iniţial).
Aici ne interesează numai repartizarea pe zile (nu şi pe orele zilei), încât vom şterge la un moment dat, coloana $ore
; mai mult, vom şterge şi coloana $zl
– fiindcă vrem să probăm şi să punem la punct algoritmul de repartizare pe zile vizat în [1].
Dar înainte de aceasta, să vedem pentru orarul iniţial, dacă orele profesorilor au fost repartizate omogen pe zile (pentru clase, distribuţia este omogenă: la fiecare clasă, numărul de ore pe zi variază cu cel mult 1). Ca şi în [1], prevedem o funcţie care să ne tabeleze numărul de ore pe zi pentru fiecare profesor:
byPrClOre <- function(byPC) { addmargins(table(byPC[c('prof', 'zl')])) %>% as.data.frame(.) %>% spread(., zl, Freq) %>% .[order(-.$Sum), ] } byp <- byPrClOre(orar) # pentru întregul orar (puteam alege şi doar un subset)
Pentru a-l reda aici, să despărţim tabelul byp
(obiect data.frame, cu 60 de linii) furnizat de această funcţie, în trei părţi de câte 20 de rânduri (separate prin câte două spaţii):
D <- data.frame(c1=1:20, c2=rep(" ",20), c3=1:20, c4=rep(" ",20), c5=1:20) D$c1 <- byp[1:20, ] D$c3 <- byp[21:40, ] D$c5 <- byp[41:60, ] names(D) <- NULL sink("3col.txt") print(D, row.names=FALSE, width=100) sink()
Prin sink()
, am redirectat ieşirea pe un fişier, fiindcă pe ecran nu încap toate caracterele unei linii din tabel (şi am mărit la 100, lăţimea uzuală de afişare a liniei):
prof Jo Lu Ma Mi Vi Sum prof Jo Lu Ma Mi Vi Sum prof Jo Lu Ma Mi Vi Sum
Sum 181 187 184 181 181 914 P25 4 4 4 4 4 20 P01 4 3 4 0 2 13
P13 6 6 6 5 5 28 P26 4 4 4 4 4 20 P46 4 0 3 0 6 13
P53 6 6 6 5 5 28 P15 4 4 4 5 2 19 P42 3 4 0 4 0 11
P54 6 6 7 6 3 28 P18 5 4 4 4 2 19 P56 1 2 3 1 2 9
P41 3 6 6 6 6 27 P29 3 3 3 5 5 19 P35 0 3 2 0 2 7
P51 4 6 5 6 5 26 P38 4 3 4 4 4 19 P20 0 2 0 0 4 6
P57 5 6 5 6 4 26 P11 2 5 3 4 4 18 P24 0 2 0 2 1 5
P05 5 5 5 5 5 25 P23 3 3 3 5 4 18 P39 0 1 2 2 0 5
P14 5 5 5 4 5 24 P28 4 3 3 4 4 18 P43 0 2 0 0 3 5
P03 4 4 5 5 5 23 P32 4 4 3 4 3 18 P55 0 3 0 2 0 5
P17 4 5 5 4 5 23 P33 4 3 4 4 3 18 P44 1 0 2 0 1 4
P48 5 4 5 4 5 23 P37 3 4 4 4 3 18 P45 4 0 0 0 0 4
P10 5 4 5 4 4 22 P58 3 4 5 4 2 18 P50 2 2 0 0 0 4
P19 5 5 4 4 4 22 P12 3 3 3 4 4 17 P52 0 1 1 0 2 4
P21 4 5 5 4 4 22 P02 3 4 3 2 4 16 P59 2 0 2 0 0 4
P04 4 2 5 5 5 21 P08 4 3 3 3 3 16 P36 0 0 0 0 3 3
P09 4 4 5 5 3 21 P16 3 2 4 4 3 16 P49 3 0 0 0 0 3
P22 5 3 4 5 4 21 P27 3 3 3 3 4 16 P40 0 0 1 1 0 2
P06 4 5 4 4 3 20 P31 3 4 3 2 4 16 P30 0 1 0 0 0 1
P07 4 4 2 5 5 20 P47 3 3 3 3 4 16 P34 0 0 0 1 0 1
Totalul de ore acoperit de tabelul obţinut prin funcţia de mai sus este 914 (nu 925), fiindcă table()
a produs contingenţa valorilor diferite de NA
, din coloanele indicate 'prof'
şi 'zl'
– încât s-au ignorat cele 11 valori NA
din coloana $zl
.
La profesorii cu peste 25 de ore pe săptămână, avem două cazuri de repartiţie neuniformă: P54
şi P41
au într-o zi 3 ore şi în celelalte câte 6 sau 7 ore (ar mai fi două cazuri, dar mai puţin importante: 4 ore într-o zi şi 6 în altele). La cei 14 profesori cu numărul de ore între 20 şi 25, avem 4 repartiţii neomogene (două fiind să zicem, mai „grave” – cu 2 ore într-o zi şi 5 în altele).
Avem situaţii similare acestora şi pentru cei 19 profesori cu 16-19 ore pe săptămână, etc. Dar să nu uităm că repartizarea redată mai sus este aceea din orarul final, ţinând seama deci şi de diversele restricţionări existente asupra zilelor alocate profesorilor (mai ales pentru cei cu puţine ore pe săptămână); mai încolo vom obţine şi noi o distribuţie a celor 914 ore pe zilele săptămânii, dar necondiţionată (şi nici finalizată într-un orar propriu-zis) – deci intenţia iniţială de a compara cele două repartizări pe zile a orelor, ar fi nelalocul ei.
vezi Cărţile mele (de programare)