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

Încă un experiment, pe orarul unei școli (V)

limbajul R | orar şcolar
2023 oct

[1] Problema orarului școlar echilibrat și limbajul R

[2] V. Bazon - Orare școlare echilibrate și limbajul R https://books.google.ro/books?id=aWrDEAAAQBAJ

[3] Transformarea fișierului PGN în obiect de date (I)

Într-un târziu, ne vom baza pe funcțiile din [2] mount_days(), mount_hours() și reduce_gaps(), pentru a repartiza pe zile lecțiile prof|cls (echilibrat, față de zile, clase și profesori), a monta pe orele zilei lecțiile dintr-o aceeași zi și respectiv, pentru a reduce (cam pe cât se poate) numărul de ferestre din orarul rezultat.
Dar mai întâi, privim iarăși la clasele cuplate – organizând mai bine față de tabelul anterior Twc, datele asociate acestora în orarul inițial; scopul este acela de a repartiza pe zile (înaintea celorlalte) lecțiile anumitor cuplaje de profesori, la clasele cuplate existente.

Orar prealabil pentru lecțiile cuplajelor la clase cuplate

Printre cele 928 de lecții din setul CDL, avem pe anumite discipline câteva lecții ale unor cuplaje de profesori, la clase cuplate; de exemplu, un cuplaj de trei profesori are de făcut două lecții de "Germană" la clasele 11ABD: fiecare dintre aceste două lecții angajează într-o aceeași zi și oră, trei grupe de elevi constituite din elevii amestecați ai celor trei clase.
Pentru a nu „urăți” peste măsură funcțiile de alocare (pe zile și pe orele zilei), alegem soluția cea mai simplă: convenim din start asupra unui orar de desfășurare a lecțiilor P23|C23 unde P23 este un cuplaj de doi sau de trei profesori, iar C23 este o pereche sau un triplet de clase (a revedea tabelul anterior Twc1); numărul acestor lecții nu este prea mare, încât preferăm să elaborăm manual – sau mai degrabă, interactiv – orarul respectiv.

Lansăm o nouă sesiune de lucru cu R, pe baza următorului program:

# orr_cls_cupl.R  (orar prealabil, la perechile|tripletele de clase cuplate)
rm(list = ls())  # elimină obiectele din sesiunea precedentă
library(tidyverse)
CDL <- readRDS("CDL.rds")  # 928 lecții prof|obj|cls|zi|ora (orarul original)
load("Tw123.Rda")  # dicționarele dependențelor
Zile <- c("Lu", "Ma", "Mi", "Jo", "Vi")
CDL <- CDL %>% 
       mutate(zi = factor(zi, levels = Zile, ordered=TRUE),
              prof = factor(prof)) %>%
       select(prof, cls, ora, zi)        

Avem clase cuplate, câte două sau câte trei, pe "Germană" și pe "Desen/Muzică"; determinăm cuplajele de doi sau trei profesori, care fac lecțiile respective:

P23 <- union(names(Tw2), names(Tw3))  # cuplajele de doi sau trei profesori
P23 <- P23[grepl("Ds", P23) | grepl("LG", P23)]  # "Desen/Muzică" sau "Germană"
    # "Ds1Mz1" "LG1LG4" "LG2LG1" "LG2LG3" "LG3LG4" "LG3LG1LG4"

Setul tuturor lecțiilor acestora (pe clase cuplate sau pe grupe ale unei aceleiași clase) este:

L123 <- CDL %>% filter(prof %in% P23) %>% droplevels()
> print(L123 %>% arrange(prof, cls) %>% head(13))
         prof cls ora zi
    1  Ds1Mz1 10F   2 Vi  # "pe grupe" ale clasei
    2  Ds1Mz1 10B   3 Jo
    3  Ds1Mz1 10C   6 Ma
    4  Ds1Mz1 10D   4 Jo
    5  Ds1Mz1 10E   4 Vi
    6  Ds1Mz1  9A   7 Ma  # cuplaj de clase "9AB"
    7  Ds1Mz1  9B   7 Ma
    8  Ds1Mz1  9C   5 Jo  # "9CD"
    9  Ds1Mz1  9D   5 Jo
    10 LG1LG4 10F   4 Mi  # "10EF"
    11 LG1LG4 10F   5 Jo
    12 LG1LG4 10E   4 Mi
    13 LG1LG4 10E   5 Jo

Observăm că 10F apare singură (nu împreună cu vreo altă clasă) în ora 2 din ziua Vi – deci Ds1Mz1 face lecția la 10F „pe grupe” (analog, la alte clase de-a 10-a): în săptămâna curentă Ds1 intră la grupa-1 și Mz1 intră la grupa-2 și invers, în săptămâna următoare (sau echivalent, dar mai convenabil: clasa întreagă 10F alternează săptămânal "Desen" și "Muzică").
În schimb, clasele 9A și 9B apar la Ds1Mz1 într-o aceeași zi și oră – deci în ora 7 din ziua Ma:
Ds1 intră la 9A și Mz1 intră la 9B în săptămâna curentă, și invers în cea următoare.
Situații asemănătoare avem pentru "Germană" (dar fără alternanță săptămânală): elevii claselor 10EF sunt categorisiți, probabil ca „începători” sau „avansați”, rezultând două noi clase, la care intră într-o aceeași zi și oră (de două ori pe săptămână) LG1 și respectiv LG4.

Deocamdată, ne interesează numai lecțiile pe clase cuplate – deci numai perechile sau tripletele de linii din setul L123 pe care avem aceleași valori prof|zi|ora (diferind în perechea sau tripletul respectiv, numai câmpul cls):

L23 <- L123 %>% count(prof, zi, ora) %>% 
       filter(n >= 2)  # Lecțiile (fără 'cls') cuplajelor la clasele cuplate
L23 <- right_join(L23, L123) %>%  # exclude din L123 liniile neaflate în P23
       filter(!is.na(n))

Acum putem observa în treacăt, cum sunt distribuite pe zile în orarul original, lecțiile cuplajelor de profesori la clasele cuplate:

> cz <- L23 %>% select(prof,zi,ora) %>% distinct() %>% select(prof,zi)
> print(table(cz$zi))
    Lu Ma Mi Jo Vi 
     3  6  4  7  4  # cuplaje de clase (perechi sau triplete), pe zi

Noi încercăm să facem un orar echilibrat (din mai multe puncte de vedere); astfel că vrem să apară cam același număr de cuplaje de clase, în fiecare zi (nu 3 într-o zi și 7 într-o alta).

Dar chiar, vrem mai mult: rămăseseră 5 clase 10BCDEF, la care Ds1Mz1 trebuie să intre „pe grupe” (la fiecare dintre ele); nu vedem niciun motiv, pentru care să nu putem constitui (analog cu 9AB) de exemplu, clasele cuplate 10BD și 10EF (lăsând numai 10C „pe grupe”)…
Dar oare… clasa 10A face altfel decât celelalte clase a 10-a, "Desen" și "Muzică"?
Trebuie să verificăm:

> CDL %>% filter(cls=="10A") %>% 
          filter(grepl("Ds1", prof) | grepl("Mz1", prof))
    prof cls ora zi
1 LE3Mz1 10A   3 Mi  # Muzică/Engleză  (??)
2    Ds1 10A   4 Mi  # Desen

Deci 10A face "Desen" o oră pe săptămână (nu jumătate de oră, cum fac toate clasele a 9-a și a 10-a), iar drept "Muzică"… chiar ciudat: Mz1 împarte clasa cu LE3 ("Engleză")!

Este clar că am greșit ceva, undeva pe parcurs. Confruntând orarul clasei 10A din CDL cu cel din fișierul PDF original – constatăm că Mi ora 3 trebuia să fie LE3 (nu LE3Mz1) și Mi ora 4 trebuia să fie Ds1Mz1 (nu Ds1); am greșit nu „pe parcurs”, ci din start, când am editat în Mousepad datele extrase prin Ghostscript din fișierul PDF, constituind fișierul "CDL.txt".
Pentru orice eventualitate, în CDL.txt am făcut imediat corecția necesară; dar (câtă vreme ne putem zice că nu s-au strecurat și alte greșeli) nu este necesar s-o luăm de la capăt. Corectăm ad-hoc în setul CDL (și reconstituim CDL.rds):

> CDL[which(with(CDL, prof=="LE3Mz1") == TRUE), "prof"] <- "LE3" 
> CDL[which(with(CDL, cls=="10A" & prof=="Ds1") == TRUE), "prof"] <- "Ds1Mz1" 
> saveRDS(CDL, "CDL.rds")

Mai avem de corectat în dicționarele de dependențe, eliminând "LE3Mz1":

> Tw1$LE3 <- NULL
> Tw1$Mz1 <- c("Ds1Mz1")
> Tw2$Ds1Mz1 <- c("Ds1", "Mz1")
> Tw2$LE3Mz1 <- NULL
> save(Tw1, Tw2, Tw3, file="Tw123.Rda")

Dacă relansăm acum "orr_cls_cupl.R", singura modificare față de datele redate mai sus constă în faptul că în L123 apare și 10A în contul lui Ds1Mz1; deci vom putea completa cuplajele de clase intenționate mai sus (10BD și 10EF) cu 10AC (încât și la clasele a 10-a, Ds1Mz1 va putea face lecțiile pe clase cuplate, în loc de „pe grupe” ale câte uneia).

Acum împărțim liniile din L23, încât în fiecare grup de linii să avem aceleași valori prof, zi și ora (diferind doar cls); apoi asociem acestor grupuri de linii câte un „tabel” cu același număr de linii ca grupul respectiv, conținând pe o coloană profesorul respectiv și pe o a doua coloană, vectorul claselor (repetat și acesta pe fiecare linie); reunim toate tabelele, ordonăm după prof și cls liniile din „tabelul” rezultat și apoi, adăugăm coloana zi pe care înscriem repetat de sus în jos, valorile din vectorul Zile:

L23 <- L23 %>% 
       select(prof, zi, ora, cls) %>% 
       split(list(.$prof, .$zi, .$ora)) %>% 
       discard(function(K) nrow(K) == 0) %>%
       map_dfr(., function(K) 
                  data.frame(
                       prof = K$prof[1], 
                       cls = paste(K$cls, collapse = " ")
                  )) %>%
       arrange(prof, cls) %>% 
       mutate(zi = factor(rep_len(Zile, nrow(.)), 
                          levels = Zile, ordered = TRUE))
print(L23)
                prof         cls   zi
        1     Ds1Mz1       9A 9B   Lu
        2     Ds1Mz1       9C 9D   Ma
        3     LG1LG4     10E 10F   Mi
        4     LG1LG4     10E 10F   Jo
        5     LG2LG1     10A 10B   Vi
        6     LG2LG1     10A 10B   Lu
        7     LG2LG1     10C 10D   Ma
        8     LG2LG1     10C 10D   Mi
        9     LG2LG1     12E 12F   Jo
        10    LG2LG1     12E 12F   Vi
        11    LG2LG1     12E 12F   Lu
        12    LG2LG1       9A 9B   Ma
        13    LG2LG1       9A 9B   Mi
        14    LG2LG3     11E 11F   Jo
        15    LG2LG3     11E 11F   Vi
        16    LG2LG3     11E 11F   Lu
        17    LG2LG3       9E 9F   Ma
        18    LG2LG3       9E 9F   Mi
        19    LG2LG3       9E 9F   Jo
        20 LG3LG1LG4 11A 11B 11D   Vi
        21 LG3LG1LG4 11A 11B 11D   Lu
        22 LG3LG1LG4 12A 12B 12D   Ma
        23 LG3LG1LG4 12A 12B 12D   Mi
        24    LG3LG4     10E 10F   Jo

Am obținut astfel o repartizare pe zile a lecțiilor cuplajelor de profesori la cele 24 de perechi sau triplete de clase cuplate – și anume, o repartizare echilibrată: fiindcă am etichetat cu Zile de patru ori complet și încă o dată dar fără Vi, rezultă că în primele 4 zile avem câte 5 lecții la clase cuplate, iar în a 5-a avem 4 lecții.
Dar să observăm că 10EF apare de două ori în ziua Jo: în linia 4 cu LG1LG4 și în linia 24 cu LG3LG4; reetichetăm linia 24, pentru a evita situația menționată:

> L23[24, 3] <- "Vi"

Acum fiecare dintre clasele cuplate apare cel mult o singură dată pe zi:

> print(addmargins(table(L23[c('cls','zi')])))
        cls           Lu Ma Mi Jo Vi Sum
          10A 10B      1  0  0  0  1   2
          10C 10D      0  1  1  0  0   2
          10E 10F      0  0  1  1  1   3
          11A 11B 11D  1  0  0  0  1   2
          11E 11F      1  0  0  1  1   3
          12A 12B 12D  0  1  1  0  0   2
          12E 12F      1  0  0  1  1   3
          9A 9B        1  1  1  0  0   3
          9C 9D        0  1  0  0  0   1
          9E 9F        0  1  1  1  0   3
          Sum          5  5  5  4  5  24

Distribuțiile zilnice individuale, pentru cuplajele de profesori care fac ore la clasele cuplate sunt deasemenea, echilibrate:

> print(addmargins(table(L23[c('prof','zi')])))
        prof        Lu Ma Mi Jo Vi Sum
          Ds1Mz1     1  1  0  0  0   2
          LG1LG4     0  0  1  1  0   2
          LG2LG1     2  2  2  1  2   9
          LG2LG3     1  1  1  2  1   6
          LG3LG1LG4  1  1  1  0  1   4
          LG3LG4     0  0  0  0  1   1
          Sum        5  5  5  4  5  24

În sfârșit să instituim în L23 și clasele cuplate 10AC, 10BD și 10EF, în contul lui Ds1Mz1:

> dm10 <- data.frame(prof = rep("Ds1Mz1", 3), 
                     cls = c("10A 10C", "10B 10D", "10E 10F"), 
                     zi = c("Jo", "Mi", "Vi"))
> L23 <- rbind(L23, dm10)
> saveRDS(L23, "cls23_days.RDS")

În "cls23_days.RDS" avem acum ceea ce numisem cam exagerat „orar prealabil”, de fapt numai o repartizare pe zile (nu și pe orele zilei, cum se cuvenea unui „orar”), pentru lecțiile cuplajelor de doi sau trei profesori, la clasele cuplate (câte două sau câte trei). Se prea poate ca ulterior, să modificăm (dar în aceleași condiții de echilibru ca mai sus) repartizarea pe zile din L23; așa că se cuvine să amânăm adăugarea unei coloane în care să etichetăm lecțiile respective cu orele zilei.
În fond, zi și ora sunt variabile independente, deci chiar se cuvine, să le tratăm separat!

Rupem de-acum legătura cu orarul original, eliminând din CDL coloanele inițiale zi și ora; în plus, trebuie să eliminăm din setul CDL lecțiile pe care deja le-am repartizat pe zile prin L23 – urmând să ne ocupăm de montarea pe zile (echilibrată) a lecțiilor rămase:

LSS <- CDL %>% select(prof, cls)
qls <- sapply(L23$cls, function(Q) strsplit(Q, " ")[[1]]) %>% 
       unlist() %>% as.vector() %>% unique()
Del <- LSS %>% filter(prof %in% L23$prof & cls %in% qls)
LSS <- anti_join(LSS, Del) %>% droplevels()  # 870 obs. of  2 variables
saveRDS(LSS, "lessons.RDS")

În lessons.RDS avem 870 de lecții prof|cls, unde prof reprezintă fie un profesor „real”, fie un cuplaj (de doi profesori) diferit de cele înregistrate în L23; urmează să repartizăm pe zile, în mod echilibrat, aceste lecții.

vezi Cărţile mele (de programare)

docerpro | Prev | Next