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

Distribuţia orară a distribuţiei pe zile a orelor şcolii (II)

orar şcolar | R
2021 feb

Distribuţia numărului de ferestre - reluare

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

Formularea CVS a orarelor "tibble" ale zilelor

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.

Aplicaţie de reducere interactivă a ferestrelor

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

docerpro | Prev | Next