[1] V.Bazon - De la seturi de date și limbajul R, la orare școlare (Google Books)
[2] V. Bazon - Orare școlare echilibrate și limbajul R (Google Books)
În [3] am demarat pachetul hours2lessons
: am constituit întâi scheletul pachetului, specificând în fișierul 'DESCRIPTION
' pachetele care trebuie importate (de exemplu, dplyr
și igraph
) și în 'NAMESPACE
' funcțiile necesare din fiecare; apoi am introdus funcția scale_prof_cls()
care asociază setului de lecții prof|cls
indicat, anumiți coeficienți de ierarhizare ("betweenness" pe graful profesorilor și respectiv, al claselor — după numărul de clase, respectiv profesori, în comun).
Apoi, am introdus funcția mount_hours()
prin care se obține un orar prof|cls|ora
— dar, simplificând lucrurile pentru o primă tentativă de împachetare, am omis cazul când avem lecții cuplate, precum și cazul când există tuplaje…
Pentru a ține seama de existența unor cuplaje (eventual și de tuplaje, asociate suplimentar lecțiilor zilei), am avea de extins funcția mountHtoCls()
— care apare (v. [3]) în interiorul funcției mount_hours()
— adăugând condiția ca lecțiile celor angajați în câte un cuplaj să nu se suprapună (iar pentru tuplaje… dimpotrivă: lecțiile dintr-un același tuplaj trebuie să cadă în câte o aceeași oră).
Cum am convenit anterior (v. [3]), un cod prof
de lungime 6 reprezintă un cuplaj; avem de filtrat prof
după lungimea codurilor și dacă este cazul, de furnizat o listă care să indice dependențele de asigurat la alocarea pe ore a lecțiilor celor angajați în cuplaje.
Lansăm R
din directorul sursă al pachetului nostru, restaurăm pachetul în memorie și instituim funcția necesară:
> library(devtools) > load_all() # directorul curent este cel care conține sursa pachetului > use_r("get_twins")
#' Depistează cuplajele și dependențele de alocare pe ore induse de acestea #' @param LSS data.frame cu lecțiile prof|cls #' @return NULL dacă nu există cuplaje; altfel, lista care indică bilateral, #' pe fiecare membru al unui cuplaj și pe fiecare cuplaj, profesorii #' și cuplajele de care depinde alocarea pe ore a lecțiilor sale #' get_twins <- function(LSS) { if(all(nchar(LSS$prof) == 3)) return(NULL) # nu există cuplaje P36 <- split(LSS, ~ nchar(LSS$prof)) %>% map(function(LS) LS %>% pull(.data$prof) %>% unique() %>% sort()) P3 <- P36[[1]] # vectorul profesorilor angajați în cuplaje P6 <- P36[[2]] # vectorul cuplajelor Tw1 <- map(P3, function(P) P6[grepl(P, P6)]) %>% setNames(P3) %>% purrr::compact() Tw2 <- map(P6, function(PP) { p1 <- substr(PP, 1, 3) p2 <- substr(PP, 4, 6) setdiff(c(p1, p2, Tw1[[p1]], Tw1[[p2]]), PP) %>% unique() }) %>% setNames(P6) %>% purrr::compact() list(Tw1, Tw2) }
Chiar dacă este vorba de o funcție internă pachetului (a observa că lipsește specificația "@extern
"), o putem invoca (având sursele în memorie) și o putem testa direct (fără a instala pachetul):
> check() # OK! (dar nu înseamnă totdeauna, că lucrurile sunt în regulă) > LSS <- readRDS("~/24dec/R12_ldz.RDS") > TW <- get_twins(LSS[["Jo"]]) # lecțiile prof|cls din ziua "Jo" > TW[[1]] $Ds1 "Ds1Mz1" $Fr1 "Fr1Fr2" "Fr1Gr1" $Fr2 "Fr1Fr2" $Gr1 "Fr1Gr1" $Mz1 "Ds1Mz1" > TW[[2]] $Ds1Mz1 "Ds1" "Mz1" $Fr1Fr2 "Fr1" "Fr2" "Fr1Gr1" $Fr1Gr1 "Fr1" "Gr1" "Fr1Fr2"
Va trebui să adăugăm două condiții: dacă P
este o cheie din TW[[1]]
, lecția (P, cls)
nu va putea fi într-o aceeași oră cu niciuna dintre lecțiile (TW[[1]][P], cls)
; analog, pentru lecțiile cu cheile în TW[[2]]
.
Ne așteptăm ca tuplajele, dacă există, să fie specificate pe câte o linie a unui set suplimentar prof|cls
, unde prof
înregistrează profesorii care vor trebui să se afle (câte unul, sau eventual câte doi) într-o aceeași oră, la câte una dintre "clasele" înregistrate în cls
— de exemplu:
prof cls 1 Ds1 Mz1 10E 11E 2 Fr2 Fr3 Gr1 09A 09C 09D 3 Fr1 Fr2 Fr3 Gr2 10B 10C 10E 4 Fr1 Fr2 Gr1 11C 11E
Ds1
și Mz1
trebuie să intre într-o aceeași oră, la clasele 10E
și respectiv 11E
(și eventual invers, în săptămâna următoare — dacă orele de "Desen" și "Muzică" sunt alocate la clasele respective "pe câte o jumătate de oră/săptămână").
Lecțiile de "Franceză" și "Germană" pot angaja într-o aceeași oră, mai mulți profesori și mai multe clase; adesea, elevii reuniți ai claselor respective sunt regrupați în noi "clase" (pentru care păstrăm numele de clasă, chiar dacă acum "09A
" cuprinde "începători" din două sau mai multe dintre clasele respective); în exemplul de mai sus (v. linia 2), Fr2
va trebui să intre la "clasa" 09A
într-o aceeași oră în care Fr3
intră la 09C
și Gr1
intră la 09D
.
Dacă într-un tuplaj, numărul de profesori este mai mare decât cel de clase (v. liniile 3 și 4), atunci va trebui să înființăm un cuplaj de doi profesori pentru una dintre clasele respective; de exemplu, pentru linia 3: înființăm cuplajul Fr1Fr2
pentru "clasa" 10B
(deci Fr1
și Fr2
vor intra în ora respectivă la câte o grupă de elevi, din clasa constituită ad-hoc sub numele 10B
) și în aceeași oră, Fr3
va intra la 10C
și Gr2
va intra la 10E
.
Subliniem că ordinea în care se specifică profesorii și clasele din fiecare tuplaj nu este (așa de) importantă; poate că Fr1Fr2
trebuie să intre nu la grupe din 10B
, ci la grupe din 10C
(și atunci clasele trebuiau specificate prin 10C 10B 10E
, sau după caz într-o altă ordine). De fapt, clasele de pe linia respectivă desemnează acum (eventual) niște noi "clase", constituite ad_hoc (după reguli convenite de către profesorii respectivi) din elevii reuniți ai claselor inițiale; "10B
" ar desemna acea clasă care trebuie să facă "Franceză" pe grupe, iar "10E
" pe aceea, nou constituită, care are de făcut "Germană".
Subliniem că nu vom lua în seamă cazuri nefirești, precum vreun tuplaj de 5 profesori cu 3 clase (când ar trebui înființate, nefiresc, două cuplaje)… Deasemenea, nu prea are sens să ne gândim la tuplaje de genul doi profesori pe trei clase — "noile" clase ar avea fiecare, prea mulți elevi (față de normativele uzuale).
Observație. Va fi nevoie să separăm profesorii (și respectiv clasele), de pe o linie de tuplaje (de exemplu, din vectorul de lungime 1 "10B 10C 10E
" să obținem cei 3 vectori de lungime 1, reprezentând câte o clasă: "10B
", "10C
" și "10E
"); dar pentru a determina numărul de profesori din tuplaj, nu este necesar să folosim strsplit()
(urmat de unlist()
) — avem o soluție mai simplă (folosind nchar()
), știind că profesorii din setul tuplajelor sunt codificați pe câte 3 caractere.
De exemplu, din faptul că șirul din coloana prof
, linia 4 (v. exemplul redat mai sus), are lungimea 15 — deducem că în tuplajul respectiv sunt $(15 + 1)/4$ = 4 profesori; am adăugat 1 lungimii șirului, fiindcă numărul de spații separatoare este cu 1 mai mic, decât cel de profesori (nu mai avem spațiu, după ultimul profesor din șir) și am împărțit la 4 fiindcă după fiecare cod de 3 caractere (exceptându-l pe ultimul) urmează un spațiu.
Pentru clase, am putea conveni reprezentarea pe câte 3 caractere (cum se vede mai sus, cu observația subiectivă că era mai frumos "i9A
" decât "09A
") și atunci, putem găsi după același principiu (plecând de la lungimea șirului din coloana cls
) și numărul claselor din tuplaj. Dar n-am vrut să încărcăm cu încă o convenție de notare — acceptând în mod tacit notația standard a claselor, pe două sau trei caractere ("9A
", "12A
").
Introducem o funcție internă (ne-exportată) care primind setul tuplajelor, îi adaugă o coloană ora
(cu valoarea inițială 0L
), returnându-l astfel într-o listă, împreună cu un vector conținând clasele implicate în tuplaje și un altul, conținând profesorii respectivi (între care — dacă este cazul — și cuplajul de înființat, când numărul de profesori este (cu unu) mai mare ca al claselor din tuplajul respectiv):
#' Adaptează setul tuplajelor în vederea alocării în câte o aceeași oră #' a lecțiilor dintr-un același tuplaj #' @param TPL data.frame conținând tuplajele prof|cls (cu separare la spațiu #' în cadrul fiecăruia dintre cele două câmpuri) #' @return NULL dacă setul TPL este defectuos; altfel, o listă conținând #' setul prof|cls|ora ('ora' fiind inițializată cu 0), împreună cu #' doi vectori: profesorii și respectiv clasele, din tuplaje #' #' În unele cazuri (4 profesori pe 3 clase) se înființează un cuplaj #' pentru primii doi din tuplajul respectiv. on_tuples <- function(TPL) { Lp <- nchar(TPL$prof) # lungimile șirurilor din coloana 'prof' if(any(Lp > 15)) return(NULL) # este nefiresc un tuplaj cu peste 4 profesori Cls <- gsub("( |\\b)([0-9]{1}[A-Z]{1})( |\\b)", "\\1i\\2\\3", TPL$cls) # prefixează cu 'i' numele 'cls' de lungime 2 Lc <- nchar(Cls) if(any(Lp - Lc) > 4) return(NULL) # un tuplaj ar putea avea cel mult, un cuplaj for(i in which(Lp - Lc == 4)) # înființează un cuplaj, la primii doi TPL$prof[i] <- sub(" ", "", TPL$prof[i]) t_prof <- lapply(TPL$prof, function(V) strsplit(V, " ")[[1]]) %>% unlist() %>% as.vector() %>% unique() t_cls <- lapply(TPL$cls, function(V) strsplit(V, " ")[[1]]) %>% unlist() %>% as.vector() %>% unique() list(TPL %>% mutate(ora = 0L), t_prof, t_cls) }
Testăm iarăși în maniera ad-hoc, pe un set de tuplaje adus dintr-un program de care ne-am ocupat mai demult în [1] (set care conținea tuplajele corespunzătoare fiecăreia dintre zilele de lucru; am ales pe cele din ziua "Mi
"):
> check() # OK (!) > LT <- readRDS("~/24nov/tuplaje_b.RDS") %>% filter(zl == "Mi") %>% on_tuples() > LT [[1]] # setul tuplajelor (adaptat) cls prof zl ora 1 9A 9C 9D Fr2 Fr3 Gr1 Mi 0 2 10B 10C 10E Fr1Fr2 Fr3 Gr2 Mi 0 3 11C 11E Fr1Fr2 Gr1 Mi 0 [[2]] # profesorii din tuplaje; s-a înființat un cuplaj, membru în două tuplaje [1] "Fr2" "Fr3" "Gr1" "Fr1Fr2" "Gr2" [[3]] # clasele din tuplaje [1] "9A" "9C" "9D" "10B" "10C" "10E" "11C" "11E"
Va trebui să căutăm cuplajele înființate (aici, Fr1Fr2
) în vectorii returnați de get_twins()
și eventual, să le adăugăm acestora — urmând să respectăm condiția (de cuplare) ca lecțiile lui Fr1Fr2
la 10B
, respectiv 11C
, să nu se suprapună cu vreo lecție la care Fr1
, respectiv Fr2
, intră singur și deasemenea, condiția (de tuplare) ca profesorii din tuplajul din linia 2 (inclusiv, cel nou Fr1Fr2
), respectiv din linia 3, să intre în câte o aceeași oră 1:7
, la clasele din tuplajul respectiv.
Vom rezolva condiția de tuplare folosind dinamic coloana ora
— setând sau resetând (după situația curentă a alocării lecțiilor) valorile acesteia pe o linie sau alta.
Dacă, pe lângă setul LSS
al lecțiilor (incluzând și lecțiile "pe grupe" de clasă, înregistrate la profesori "fictivi"), este furnizat și un set TPL
de tuplaje — atunci extragem prin on_tuples()
vectorul profesorilor și pe cel al claselor care apar în tuplaje și verificăm dacă on_tuples()
a înființat noi cuplaje (când va fi constatat în vreun tuplaj un număr de profesori mai mare cu 1 decât cel de clase); pentru fiecare nou cuplaj, adăugăm în LSS
lecțiile acestuia (din toate tuplajele unde a apărut).
De exemplu, pentru tuplajele returnate mai sus de on_tuples()
, înscriem în LSS
liniile Fr1Fr2|10C
și Fr1Fr2|11C
(în LSS
vom avea astfel toate lecțiile care trebuie desfășurate "pe grupe" de clasă). Deasemenea… trebuie adăugate în LSS
și lecțiile individuale din tuplaje, Fr2|9A
, Fr3|9C
, Gr1|9D
(care trebuie desfășurate într-o aceeași oră), etc.
Apoi, prin get_twins()
constituim vectorii care indică pentru fiecare profesor angajat în vreun cuplaj, respectiv pentru fiecare cuplaj, profesorii și cuplajele de care depinde alocarea pe ore a lecțiilor acestuia.
De exemplu, lecția lui Fr1Fr2
la 10B
sau la 11C
nu se poate suprapune cu vreo lecție a lui Fr1
, sau a lui Fr2
și nici cu vreuna a cuplajului Fr1Gr1
, existent din start în LSS
(iar Fr1Fr2
trebuie adăugat în vectorul dependențelor lui Fr1Gr1
).
Apoi, obținem prin scale_prof_cls(LSS)
, coeficienții de ierarhizare a claselor (și respectiv profesorilor) — folosiți ulterior ca ponderi pentru alegerea aleatorie a câte unei clase, în scopul alocării pe ore a lecțiilor acesteia. Experimentele anterioare ne-au arătat că parcurgerea claselor într-o ordine aleatoare apropiată de cea crescătoare a coeficienților "betweenness" pe graful după numărul de profesori comuni fiecărei perechi de clase, are cele mai bune șanse de a trece consecutiv prin toate clasele, repartizând pe ore lecțiile fiecăreia.
Pentru a urmări și a confrunta alocările făcute pe rând profesorilor și claselor, folosim un vector cu nume (un "dicționar") prin care fiecărui profesor/cuplaj îi corespunde câte un octet în care vom seta bitul de rang i=0:6
pentru a semnala alocarea unei lecții a profesorului respectiv în ora (i+1)
.
#' Adaugă 'ora' încât oricare două lecții prof|cls|ora să nu se suprapună #' @param LSS data.frame cu lecțiile prof|cls, unde 'prof' este un #' profesor propriu-zis, sau unul fictiv (cuplaj de doi/clasă) #' @param TPL data.frame pentru tuplaje, dacă este cazul #' Un tuplaj conține 3 sau 4 profesori, pe 2, 3 sau 4 clase #' (numărul de profesori fiind cel mult cu 1 mai mare, ca al claselor) #' @return Un orar prof|cls|ora pentru ziua respectivă #' @export #' mount_hours <- function(LSS, TPL = NULL) { ### ... (150 linii) }
Corpul funcției mount_hours()
este totuși prea lung, încât aici se cuvine să-l omitem (pe de altă parte… sperăm că la un moment viitor, pachetul hours2lessons
va fi accesibil de pe CRAN).
vezi Cărţile mele (de programare)