momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

Cele 99 de șabloane orare cu ferestre

limbajul R | orar şcolar
2022 oct

[1] De capul meu prin problema orarului şcolar (pe Google Play; v. extras)

[2] Reducerea ferestrelor din orarul zilei

Mai ales dacă sunt multe ferestre, am putea muta la întâmplare o clasă și dacă rezultă mai puține ferestre, putem repeta la fel pentru noul orar – până ce numărul curent de ferestre devine suficient de mic, pentru a încerca apoi diverse „mutări corectoare” concrete.
Pe orarul inițial (obținut prin mount_hours()), la fiecare profesor avem cel mult două ferestre; dar în urma unei mutări oarecare, se poate să apară și mai mult de două ferestre – depinzând de numărul de ore: pe n ore, sunt posibile până la (7-n) ferestre (unde n=2..6).

Oare câte șabloane orare cu măcar o fereastră există? (presupunând 7 ore pe zi)
De exemplu, pentru ”*-*” (două ore cu o fereastră) avem 5 posibilități, fiindcă acest șablon poate fi poziționat fie de la ora 1 a zilei, fie de la ora 2, 3, 4 sau 5; pentru ”*--*” (două ore, cu două ferestre) avem 4 posibilități; ș.a.m.d.

Constituim un fișier în care deocamdată, copiem funcția anterioară cnt_holes() și o modificăm încât să obținem din șablonul binar furnizat nu numai numărul de ferestre, dar și numărul de ore; apoi adăugăm o funcție care generează toate șabloanele binare cu număr dat de ore și de ferestre:

# cover.R
h2bin <- as.integer(c(1, 2, 4, 8, 16, 32, 64))  # măştile orelor zilei (7 ore)
cnt_hours_gaps <- function(sb) {  # sb: şablonul binar al orelor profesorului
    bits <- which(bitwAnd(sb, h2bin) > 0)  # rangurile biţilor '1'
    n <- length(bits)
    c(n, bits[n] - bits[1] + 1 - n)
}  # Numărul de ore și numărul de ferestre

patt_gen <- function(H, G) {  # Hours, Gaps
    patt <- vector("integer")
    for(S in 1:127) {  # șablonul maxim este 1111111=127
        hg <- cnt_hours_gaps(S)
        if(hg[1] == H && hg[2] == G)
            patt <- c(patt, S)
    }
    patt  # șabloanele cu H ore și G ferestre
}

Iterând patt_gen() pentru H=2:6 și G=1:(7-H) putem constata că avem în total 99 de șabloane orare cu măcar o fereastră; de exemplu,

> patt_gen(H = 2, G = 2)
[1] 9 18 36 72

ne arată cele 4 șabloane pentru 2 ore cu câte 2 ferestre: de la ora 1 avem 9 ca șablon binar, adică "1001" – care prin deplasare spre stânga cu câte o oră (altfel spus, prin dublarea valorii curente: 18, 36, 72) ne dă celelalte trei posibilități.

Ne-ar conveni deocamdată, să folosim „literali” în loc de „șabloane binare”, fiindcă putem șablona ad-hoc o linie din matricea-orar, folosind "-" și ".":

> row <- as.vector(Mop["Ef2", ])  # "12C" "-" "-" "11A" "12A" "12E" "-"
> row[row != '-'] <- "."
> sb <- paste0(row, collapse="")
[1] ".--...-"  # în loc de 57=111001 (cu orele indexate de la dreapta)

și apoi vom putea folosi șablonul rezultat drept cheie, SBC[[".--...-"]], prin care am obține „corecturile” de făcut pentru a elimina o fereastră – în cazul de față, ar fi două corecturi posibile: mutăm clasa din ora 1 fie în ora 3, fie în ora 7.
Doar că am avea de formulat astfel 99 de linii…

Putem automatiza parțial, formularea listei SBC – combinând patt_gen() cu următoarea funcție de conversie de la „binar” la șablon „literal”:

byte_as_line <- function(sb) {  # șablonul binar (integer)
    B <- rep("-", 7)  # avem 7 ore pe zi
    i <- 7
    while(sb > 0) {
        r <- sb %% 2
        if(r == 1) B[i] <- '.'
        i <- i-1
        sb <- sb %/% 2
    }
    paste0(rev(B), collapse="")  # șablonul literal
}

Generăm șabloanele binare, le convertim prin byte_as_line() și scriem într-un fișier-text (dar marcat ca „program R”) cele 99 de definiții pentru SBC:

sink("patt_of_gaps.R")  # ".R" - pentru a-l încărca ulterior în R
for(H in 2:6)
    for(G in 1:(7-H)) {
        ptg <- patt_gen(H, G)
        for(S in ptg) {
            patt <- byte_as_line(S)
            cat("SBC[[\"", patt, "\"]] <- list(h1=c(), h2=c())\n", sep="")
        }
    }
sink()

„Definițiile” rezultate astfel au forma:

SBC[[".-.----"]] <- list(h1=c(), h2=c())
SBC[["-.-.---"]] <- list(h1=c(), h2=c())
## etc.

și ne rămâne doar de completat listele de „mutări corectoare” h1 și h2 – indicând care '.' să fie mutat și unde, încât să „reparăm” o fereastră din șablonul respectiv, de exemplu:

SBC <- list() # trebuie adăugat la începutul fișierului "patt_of_gaps.R"
SBC[[".-.----"]] <- list(h1=c(1,1,3), h2=c(2,4,2))

adică: mută clasa din ora 1 fie în ora 2, fie în ora 4, respectiv mută clasa din ora 3 în ora 2 (și fereastra din ora 2 va dispărea).

Completăm cu atenția cuvenită (am preferat manual, nu algoritmic), iar la sfârșitul fișierului "patt_of_gaps.R" adăugăm secvența:

names(SBC) <- sapply(names(SBC), function(sb) {
    S <- strsplit(sb, "")[[1]]
    as.character(sum(h2bin[which(S == ".")]))
})

prin care instituim drept chei în SBC chiar șabloanele binare (convertite prin as.character()); folosisem „literali” drept chei, pentru a ne ușura completarea manuală a vectorilor h1 și h2 – dar pentru a folosi mai departe SBC este mai bine să revenim la „octeți”.
În final, salvăm lista mutărilor corectoare SBC:

> source("patt_of_gaps.R")  # cele 99 definiții de ,,mutări corectoare''
> save(SBC, file="SBC.Rda")

Subliniem că avem vreo 10 cazuri, în care vom putea acoperi numai una dintre cele două sau trei ferestre existente (și am ales „după gust”); de exemplu:

SBC[["..--.-."]] <- list(h1=c(7,7), h2=c(3,4)) #
SBC[["...--.."]] <- list(h1=c(1,1), h2=c(4,5)) #

În aceste cazuri, pentru a elimina toate ferestrele ar trebui să înlănțuim două mutări (move_cls() face numai una)… Dar probabil că nu va fi nevoie să elimităm deodată, toate ferestrele dintr-un șablon – dat fiind că vom reduce ferestrele „din aproape în aproape”.

Folosind lista SBC, următoarea funcție produce tabelul mutărilor posibile (cu h1 și h2 specificați în SBC) pentru a corecta câte o fereastră la profesorii neimplicați în cuplaje din matricea-orar curentă:

# stabileşte o gamă de "reparaţii" pentru ferestre (ne-ascunse)
cover_gaps <- function(Mop) {
    Vsb <- bin_patt(Mop)  # vectorul șabloanelor binare ale liniilor din matricea-orar
    Vsb[names(Vsb) %in% nTz2] <- 1  # NU interesează ferestrele celor fictivi
    # indecşii liniilor celor ne-fictivi, pe care avem ferestre
    B <- which(unlist(lapply(Vsb, cnt_holes), use.names=FALSE) > 0)
    if(!length(B)) return(NULL)  # ne-am mira să fie zero ferestre...
    lh1 <- lh2 <- vector("integer", 0)
    lql <- vector("character", 0)
    nc <- ncol(Mop)
    for(id in B) {
        h12 <- SBC[[as.character(Vsb[id])]]
        H1 <- h12[["h1"]]; H2 <- h12[["h2"]]
        for(i in seq_along(H1)) {
            h1 <- H1[i]; h2 <- H2[i]
            if(max(h1, h2) > nc)
                next  # ocolește mutările în afara coloanelor existente
            lql <- c(lql, Mop[id, h1])
            lh1 <- c(lh1, h1)
            lh2 <- c(lh2, h2)
        } 
    } 
    data.frame(h1 = lh1, h2 = lh2, ql = lql) 
}

De observat că în SBC sunt vizate 7 ore, însă Mop s-ar putea să aibă numai 6 coloane (toate clasele având mai puțin de 7 ore) – deci în acest caz, au trebuit excluse acele șabloane din SBC prin care corectarea ferestrei ar angaja coloana 7 (altfel, apelul ulterior move_cls() ar eșua, fiindcă în path_kempe() nu se vor putea constitui ambii vectori L1 și L2).

Anterior (v. [1], [2]) cover_gaps() (pe lângă faptul că era cam de două ori mai lungă) producea mutări corectoare numai pentru cazul celor cel mult două ferestre lăsate profesorilor în cursul repartizării lecțiilor pe orele zilei (prin mount_hours()); acum sunt vizate toate cele 99 de șabloane cu ferestre.
Probabil că timpul necesar reducerii de ferestre (prin search_better(), angajând acum lista SBC) va crește, dar ajungem astfel (într-un timp care este totuși suficient de scurt) la orare cu număr ceva mai mic de ferestre, față de ceea ce obțineam anterior.

Cu aceasta – probabil că încheiem seria de articole referitoare la orarul școlar începută aici, în noiembrie 2020 (da… au trecut doi ani de atunci).
Precizăm că între timp am rescris complet [1] (refăcând mai toate demersurile și programele respective) și avem o nouă carte în final, Problema orarului școlar, pe tărâmul limbajului R; cel mai comod este să o postez iarăși la GooglePlay și probabil, chiar așa voi face (deși formatul ebook nu mi se pare deloc potrivit pentru o „carte de programare”) – dar până una-alta (având în vedere motivele pentru care scriu și nu necesitățile externe) sunt și dispus să o ofer gratuit (încă în format PDF, nu ebook) oricui ar cere, dacă mă va putea încredința că are de ce să mi-o ceară…

vezi Cărţile mele (de programare)

docerpro | Prev | Next