dfBits5.RDS
reprezintă prin câte un octet, distribuţia orelor pe parcursul fiecărei zile, pentru fiecare profesor:
btm <- readRDS("dfBits5.RDS") # harta de biţi a distribuţiei pe orele zilelor str(btm) 'data.frame': 76 obs. of 6 variables: $ prof: chr "P01" "P02" "P03" "P04" ... $ Lu : int 31 31 31 15 31 15 23 15 15 15 ... $ Ma : int 31 15 15 31 31 31 15 15 15 15 ... $ Mi : int 31 31 31 31 7 31 31 31 31 31 ... $ Jo : int 63 31 31 15 31 15 31 15 31 15 ... $ Vi : int 31 31 31 31 47 31 31 31 15 23 ...
În [1] am parcurs toţi octeţii din btm
, deşi am observat că valorile (2k - 1) nu contribuie la numărul de ferestre (dintre toate valorile arătate mai sus, numai cele trei pe care le-am marcat – 23, 47, 23 – reflectă ferestre).
Bitul de rang k=0..6 din octetul respectiv indică dacă profesorul trebuie să intre la o anumită clasă în a (k+1)-a oră a zilei; de exemplu, dacă "P05
" are în orarul zilei primele 4 ore şi apoi, a şasea oră – atunci ultimii 6 biţi din octetul asociat sunt 101111, deci valoarea numerică a octetului este 1 + 2 + 22 + 23 + 25 = 47; bitul de rang 5 fiind '0' – şi anume, un „zerou semnificativ”, adică aflat între biţi '1' – deducem că profesorul are o „fereastră” în a 5 oră a zilei.
Masca binară a orei (k+1) a zilei este 2k (k=0..6):
msk <- c(1, 2, 4, 8, 16, 32, 64) # măştile binare ale orelor 1..7
Toţi octeţii din btm
au valori mai mici decât 27=128, fiindcă într-o zi sunt maximum 7 ore. bitwAnd(octet, msk)
va extinde octetul indicat la un vector de lungimea vectorului msk
şi va produce ca rezultat un vector în care valoarea de rang k este fie msk[k]
, fie 0 – după cum bitul de rang k din octet
este '1', respectiv 0. Pentru „ferestre”, ne interesează valorile 0 (zero) ale rezultatului, dar nu cele iniţiale (dacă profesorul începe de la ora a 3-a, atunci primele două valori sunt 0 – dar nu corespund unor ferestre) şi nici cele finale (dacă profesorul îşi termină orele cu a 4-a oră din zi, atunci ultimele trei valori sunt 0 – dar nu corespund unor ferestre).
În [1] (de unde am preluat pur şi simplu, cele de mai sus) am observat, dar nu cum se cuvine, acest fapt: valorile 2k - 1 (k=0..7) corespund unor distribuţii compacte de ore (biţii '1' fiind consecutivi) – deci pentru toate aceste valori din btm
numărul de ferestre este zero. S-ar fi cuvenit să considerăm matricea formată din coloanele 2:6 din btm
(iar prin as.matrix()
atributele de nume din data.frame se păstrează) şi să anulăm întâi toate valorile 2k - 1, care nu contribuie la numărul de ferestre:
bbm <- as.matrix(btm[, 2:6]) bbm[ bbm[ ,] %in% c(1, 3, 7, 15, 31, 63, 127) ] <- 0 > str(bbm) num [1:76, 1:5] 0 0 0 0 0 0 23 0 0 0 ... - attr(*, "dimnames")=List of 2 ..$ : chr [1:76] "P01" "P02" "P03" "P04" ... ..$ : chr [1:5] "Lu" "Ma" "Mi" "Jo" ...
Simplificăm funcţia gaps()
din [1], pentru a obţine numărul de ferestre dintr-o distribuţie orară indicată printr-un octet nenul:
gaps <- function(oct) { # oct > 0 şi oct < 128 bH <- bitwAnd(oct, msk) # dacă oct=47: 1 2 4 8 0 32 0 H <- which(bH == 0) # 5 7 s <- 1 # va indica prima valoare nenulă din bH (indecşii încep cu 1, nu 0) d <- 7 # va indica ultima valoare nenulă din bH while(bH[s] == 0) s <- s + 1 s <- s - 1 while(bH[d] == 0) d <- d - 1 length(H[H > s & H < d]) # numărul de zerouri semnificative ("ferestre") }
Acum obţinem (parcă mai clar ca în [1]) o statistică a numărului de ferestre:
NG <- lapply(bbm, function(oct) if(oct==0) 0 else gaps(oct)) print(sum(unlist(NG))) # 320 ore-fereastră pe săptămână for(i in 1:5) print(sum(unlist(NG == i))) #[1] 85 ## ferestre de o singură oră (pe săptămână) #[1] 64 ## ferestre de câte două ore (consecutive sau nu) #[1] 30 ## ferestre de câte 3 ore (consecutive sau nu) #[1] 3 ## ferestre de câte 4 ore #[1] 1 ## ferestre de câte 5 ore
Încheiem această reluare, observând că în medie avem 320/5=64 de ferestre pe zi, dintre care (în medie) 85/5=17 de câte o singură oră, 64/5≈13 de câte două ore (posibil, neconsecutive), 30/5=6 de câte 3 ore (posibil, neconsecutive); desigur, cele 6 ferestre de câte 3 ore înseamnă 6×3=18 ferestre de câte o oră.
Amintim din [1] că tmt5.RDS
este o listă cu 5 obiecte "tibble", reprezentând orarele pentru fiecare zi (în total fiind sau 240, sau 241 de ore pe zi):
TMT <- readRDS("tmt5.RDS") str(TMT) List of 5 $ Lu: tibble [241 × 3] (S3: tbl_df/tbl/data.frame) ..$ prof: Ord.factor w/ 76 levels "P01"<"P02"<"P03"<..: 1 16 26 33 ... ..$ cls : chr [1:241] "10A" "10A" "10A" "10A" ... ..$ ora : int [1:241] 1 2 3 4 5 2 1 3 4 5 ... $ Ma: tibble [240 × 3] (S3: tbl_df/tbl/data.frame) $ Mi: tibble [240 × 3] (S3: tbl_df/tbl/data.frame) $ Jo: tibble [241 × 3] (S3: tbl_df/tbl/data.frame) $ Vi: tibble [240 × 3] (S3: tbl_df/tbl/data.frame)
Am văzut mai sus că în aceste orare zilnice apar multe ferestre; le vom reduce deocamdată, pe cât vom putea, printr-o aplicaţie similară cu /recast – dar în acest scop, avem de formulat un fişier CSV corespunzător datelor din obiectele tibble conţinute în lista tmt5.RDS
(în treacăt fie zis, ne-am deprins să angajăm formatul CSV; alternativa – superioară ca posibilităţi de exploatare în programe – este formatul JSON):
# rds2csv.R library(tidyverse) TMT <- readRDS("tmt5.RDS") Zile <- c("Lu", "Ma", "Mi", "Jo", "Vi") for(zi in Zile) { orar <- TMT[[zi]] %>% arrange(prof) %>% split(.$prof) %>% map_df(., function(K) { spread(K, ora, cls) }) orar <- orar[, c('prof', sort(colnames(orar)[-1]))] write_csv(orar, file = "tmt5.csv", na = "-", append=TRUE, col_names=TRUE) }
Poate că nu era neapărat necesar, dar am avut grijă ca în toate cele 5 orare, să avem aceeaşi ordine a coloanelor (după orele 1..7). În loc de NA
(înscris prin spread()
pentru cazul când lipseşte valoarea în $cls
) am optat să înscriem în CSV caracterul "-".
Fişierul rezultat "tmt5.csv
" conţine unul după celălalt (sub câte un acelaşi antet) cele 5 orare zilnice; selectăm câteva linii:
prof,1,2,3,4,5,6,7 # antet P01,10A,10B,12B,12D,8D,-,- P02,11C,12E,6D,7A,7E,-,- ... P24,6A,9D,-,6B,-,-,7C # 3 ferestre, neconsecutive P25,12C,5C,-,-,7A,-,- ...
Avem de copiat din "tmt5csv
" orarul uneia sau alteia dintre zile (dar fără antet) şi de „pastat” în caseta <textarea>
a aplicaţiei interactive de reducere a ferestrelor; aceasta trebuie să permită mutarea claselor dintr-o oră în alta, evitând suprapunerile.
Înfiinţăm aplicaţia (widget jQuery) /dayRecast
, analogă cu /recast. Redăm aici situaţia ferestrelor rămase după ce am aplicat o serie de operaţii SWAP
pe orarul primei zile din "tmt5.csv
" (şi fiind mulţumit de rezultat, am salvat prin "Export
" orarul modificat, iar ulterior l-am reîncărcat, pentru a reda aici ferestrele rămase):
Au rămas 10 ferestre (toate, de câte o singură oră), dintre cele 65 existente iniţial. Este o idee foarte bună (pentru mai târziu) de a salva şi istoricul mutărilor de clase, efectuate pe parcurs – încât să putem reconstitui traseul de operaţii SWAP
care au condus de la orarul iniţial la cel final (încât acum, alături de butonul Gaps
am avea înscris nu doar "10 |", ci "65 | 10" – reflectând şi numărul de ferestre iniţial, nu numai pe cel final); dar şi aşa, dacă adăugăm la sfârşitul handler-ului de click pentru SWAP
o instrucţiune console.log(Self.hist);
– vom putea vedea în consola browser-ului, istoricul tuturor schimbărilor efectuate prin SWAP
.
Faţă de /recast
, avem un buton nou, Gaps
; acesta restrânge tabelul HTML la profesorii care au ferestre. SWAP
mută clasa indicată prin click pe locul liber de pe aceeaşi linie (indicat iarăşi prin click) şi asigură toate interschimbările de clase necesare pe cele două coloane, încât să se păstreze clasele şi să se evite suprapunerile; în plus, calculează numărul de ferestre rezultat prin interschimbările respective şi-l afişează alături de buton (şi salvează într-o listă "History" mutarea efectuată).
Click pe numele unui profesor va restrânge tabelul HTML la liniile profesorilor care au clase în comun cu acesta.
Următoarea funcţie javaScript ne produce numărul de ferestre, corespunzător tabloului JS asociat unei linii din fişierul CSV:
function gaps(ore) { // listă ca ['P40','-','9A','-','11D','-','8F','-'] let sore = ore.slice(1).join(''); // şirul '-9A-11D-8F-' return sore.replace(/^-*/, '').replace(/-*$/, '') // '9A-11D-8F' .split('-') // tabloul ['9A', '11D', '8F'] .length - 1; // numărul de ferestre };
Deocamdată nu-i cazul să prezentăm alte elemente ale aplicaţiei respective; fişierele sursă sunt postate pe github şi sunt montate deja aici, ca /dayRecast.
vezi Cărţile mele (de programare)