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

Problema cu liste şi problemele care sunt

Excel | Gnumeric | orar şcolar | Python | R
2020 nov

O problemă cu liste

Au fost produse cumva nişte liste, fiecare fiind asociată prin primul său element, uneia sau alteia dintre componentele unei liste iniţiale L. Să se recompună lista L, adăugând fiecărei componente iniţiale, sublistele asociate acesteia.

Ca de obicei, nu problema în sine este interesantă, ci mai degrabă contextul care a generat-o (incluzând şi „micile” probleme sau aspecte intermediare).

Acum vreo două luni m-am făcut cu un tabel Excel conţinând orarul unei şcoli, orar realizat cam la repezeală cu "ascTimetables"; pentru fiecare zi, programul produsese foarte multe „ferestre”. Până să depăşească panica specifică începutului de an şcolar (setând mai exigent "ascTimetables" şi lăsând programul să rumege o zi-două), cum-necum s-a apelat într-o doară şi la un „specialist” ca mine, pentru a „repara” ferestrele din orarul furnizat iniţial.

Eu unul ştiu foarte bine că ideea cea mai bună constă în a o lua de la capăt, în loc de a te apuca să „repari”; însă nu-i timp pentru aceasta ("mâine începe şcoala") – încât am zis că n-ar fi greu să mai reduc din ferestre, folosind [1].

Şi într-adevăr, folosind aplicaţia din [2] mi-a reuşit (şi în nu mai mult de trei ore) să reduc numărul de ferestre de la circa 60 cât existau iniţial în fiecare zi, pe la maximum 15 (e drept, tot multe…). După aceasta, a apărut imediat şi problema cu „liste” formulată la început: recompune orarul săptâmânal, din orarele zilnice rezultate.

Dar să nu expediem lucrurile, ocupându-ne deja de probleme inventate; s-o luăm mai de la capăt, uitându-ne şi la problemele care sunt (dar parcă, nu se văd)!

Problemele care sunt

Prin şcoli şi facultăţi s-a făcut destulă matematică şi informatică şi altele; în fond, ce ai avut atâta timp, de învăţăt să faci, era să duci mai departe ceea ce ai învăţat (altfel ajungi să zici fără nicio ruşine, dar de fapt cum nu se poate mai ruşinos: „păi n-am învăţat asta în facultate”). Dar ajuns „în producţie” (inclusiv, în şcoli), se pare că n-ai nevoie de atâta ştiinţă; cei mai mulţi ajung în situaţia de a se ocupa fără griji ştiinţifice cu editarea de tabele Excel şi documente Microsoft-Word şi cu „pregătirea pentru Bac” – situaţie derivată din faptul deja evident (sintetizat în final la „Bac”, prin proba de „competenţe digitale”), că sub termenul pompos „informatizare” a fost mascat (cu talent şi cu simţ practic) procesul real desfăşurat la noi peste tot, acela de „microsoftizare”.

Şi dacă ai cumva de prelucrat date astfel produse, atunci eşti obligat ca în prealabil, să corectezi formatul primar şi să-l converteşti, trăgând lucrurile „înnapoi”, la ştiinţa dinaintea integrării în producţia funcţionărească.

De pe tabelul Excel pe care l-am primit a trebuit să extrag numele profesorilor şi numele claselor repartizate pe ore în fiecare zi; desigur că n-ar fi aceasta, cine ştie ce problemă, dar… alte probleme sunt.

În primul rând probabil, eu (care am de prelucrat datele respective) nu am Microsoft-Windows (deci nici Excel). Ba chiar, de ceva timp deja, am eliminat de pe sistemul meu pachetele "Office", eliberând un spaţiu imens de memorie (nu câştigul de memorie este important, totuşi LibreOffice şi OpenOffice ocupau împreună peste 1GB); important este că după această masivă eliberare, nu am avut nicio ocazie de a regreta – de exemplu, pentru a deschide un fişier Excel pot folosi Gnumeric (în loc de LibreOffice, eliminat).

Faptul că nu am şi nu folosesc Excel a avut şi un avantaj de natură cognitivă (desigur că dacă foloseşti curent Microsoft-Windows, nu-ţi baţi capul cu aşa ceva); având de-a face de-a lungul timpului şi cu diverse „tabele Excel” (din afara sistemului meu), a trebuit să mă lămuresc cumva: ce este, în ce constă, un fişier Excel ".xlsx"? Există documentaţii serioase despre acest format de fişier (a căuta "Ecma Office Open XML Part 1", sau ECMA-376-1:2016 – o documentaţie foarte bună, de circa 5000 pagini, referitoare la tipurile de document din Microsoft-Office), dar bineînţeles că am preferat să mă lămuresc numai „cât de cât”, printr-un mic experiment ad-hoc.

Să plecăm de la un mic fişier CSV, "test.csv", care („reînviind” experimentul de acum câţiva ani) imită rânduri din tabelul Excel primit iniţial:

,,Luni,,,,,,,,,,,,Marţi,,,,,,,,,,,,Miercuri,,,,,,,,,,,,Joi,,,,,,,,,,,,Vineri,,,,,,,,,,,
,,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,
7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12
"Nume Prenume",,,"XII E",,"XI A",,,,,"X E","IX E",,,,,,"XI F",,"XII E",,,,,,,,"XI F","XII E",
,"X E",,,,"IX D","X E",,,,,"XI A","XI F",,,,"IX D",,"IX E",,,,"XI A",,,,"X E",,"IX E",,"IX D"

Încărcăm "test.csv" în Gnumeric şi formatăm rândurile şi coloanele cam cum am văzut pe tabelul Excel primit iniţial (folosind desigur mouse-ul şi meniul "Format"):

Gnumeric ne-a permis să salvăm fişierul respectiv în format Excel XLSX; acum putem investiga cumva, conţinutul fişierului rezultat.

Click-dreapta pe itemul test.xlsx din fereastra aplicaţiei "File Manager" (eu folosesc thunar) ne indică aplicaţiile prin care s-ar putea „deschide” fişierul respectiv şi pe lângă 'Open with Gnumeric', apare şi 'Open with "Archive Manager"'; deschizând cu Archive Manager, selectând lista de fişiere arătată în fereastra asociată şi folosind butonul "Extract" ne-a rezultat subdirectorul exXLSX/ pe care îl redăm aici, desfăşurat prin comanda tree:

vb@Home:~/20LAR$ tree exXLSX/
exXLSX/
├── [Content_Types].xml
├── docProps
│   ├── app.xml
│   ├── core.xml
│   └── custom.xml
├── _rels
└── xl
    ├── _rels
    │   └── workbook.xml.rels
    ├── sharedStrings.xml
    ├── styles.xml
    ├── workbook.xml
    └── worksheets
        └── sheet1.xml

Este interesant să procedăm acum şi invers – să arhivăm conţinutul directorului exXLSX/ şi să verificăm dacă putem deschide în Gnumeric fişierul rezultat:

vb@Home:~/20LAR/exXLSX$ zip -r test1.xlsx .

"test1.xlsx" este ceva mai mare decât arhiva iniţială "test.xlsx" (6.7KiB, faţă de 5.8KiB), dar deschizându-l în Gnumeric obţinem acelaşi „tabel Excel” (redat în imaginea de mai sus pentru cazul lui "test.xlsx").

Acum n-avem decât să deschidem într-un editor de text fişierele .xml rezultate prin dezarhivarea fişierului "test.xlsx" şi să încercăm să înţelegem conţinutul respectiv (folosind fireşte şi documentaţii asupra XML Spreadsheet); în principiu, acestea reflectă stilurile (culori, font, dimensiuni, alinieri, etc.) aplicate pe tabelul respectiv (celulelor, rândurilor, etc.), prezintă datele înscrise în celule (specificând sub anumite tag-uri, tipul acestora, valorile propriu-zise, etc.) şi precizează anumite legături sau relaţii posibile cu alte documente sau aplicaţii "Office".

Dar pentru contextul nostru ar fi importante două aspecte (de fapt, strâns legate unul de altul). Mai întâi, fişierul "test.csv" de la care am plecat măsoară cam 400 octeţi; în schimb, fişierele XML rezultate prin dezarhivarea fişierului Excel "test.xlsx" cumulează cam 16KiB, adică de vreo 50 de ori mai mult decât ar fi necesar pentru specificarea datelor noastre. În al doilea rând, ceea ce pentru noi înseamnă „date” de prelucrat (numele profesorilor, numele claselor şi ordinea acestora), pentru Excel înseamnă specificaţii ample asupra afişării sau scrierii valorilor respective (poziţionare în cadrul paginii, stilare, categorisire şi în final, datele propriu-zise).

Excel vizează produsul final, care trebuie (doar) afişat pe ecran, sau scris pe hârtie, sau transmis spre a fi integrat ca atare într-o altă aplicaţie "Office" (sau pe care doar vrem să-l modificăm interactiv). Dacă avem de prelucrat date existente într-un fişier Excel (nu doar să ne uităm la ele şi să le mutăm cu mouse-ul dintr-un loc în altul), atunci datele propriu-zise trebuie extrase într-un format simplu, scutit de ingredientele de afişare în care sunt ambalate; în vederea prelucrării, avem nevoie de fişierul .csv, nu de fişierul .xlsx (iar Excel are şi el, ştiam, un meniu "Save as CSV").

Revenind la fişierul Excel primit iniţial (să-l numim mai departe, "CEAR.xlsx"), putem obţine fişierul CSV corespunzător folosind programul ssconvert din Gnumeric:

ssconvert CEAR.xlsx cear.csv

Arhiva corespunzătoare fişierului "CEAR.xlsx" măsoară cam 123KiB, iar fişierul obţinut "cear.csv" abia 10KiB (sau comparând cu fişierul nedezarhivat "CEAR.xlsx" – aproape jumătate din lungimea acestuia).

Ar trebui să fie clar că dacă ai de transmis date, atunci este preferabi să transmiţi fişierul text CSV şi nu fişierul Excel XLSX (dar prin microsoftizare, realitatea este deja aberantă: ca să transmită un text cât de simplu, omu' intră întâi în Microsoft-Word ca să introducă textul respectiv).

Acum, înainte de a ne apuca să „prelucrăm” (cum s-a cerut şi ne-am propus) datele aduse în "cear.csv", urmează etapa care în realitate este cea mai grea: trebuie să verificăm cu tot calmul, corectitudinea şi consistenţa acestora şi să aşteptăm lămuriri suplimentare (mulţi profesori au ca obiect "DE", chiar şi cu 11 ore la o aceeaşi clasă – care sunt de fapt obiectele? Nu trebuia scris "Tănase" în loc de "Tanase"? La clasa cutare se pare că s-au omis orele de „a doua limbă”. Etc.).

Dacă am şti sigur că datele respective sunt chiar cele produse de "ascTimetables", atunci am scăpa de o mare parte a verificărilor necesare; de exemplu, nu mai este necesar să verificăm că fiecare clasă apare (cel mult) o singură dată pe fiecare coloană – condiţie specifică unui orar, însemnând că nu există doi profesori care să intre simultan la o aceeaşi clasă.

Dar de obicei, tabelul Excel furnizat este unul editat manual după datele produse de programul care a generat orarul (făcând ulterior, diverse rearanjări şi adaosuri); sau, dacă nu este unul editat manual, seamănă foarte bine cu unul editat manual! (poate că "ascTimetables" acceptă şi "XII E" şi "XI I E", etc. şi ştie că este vorba de una şi aceeaşi clasă – dar „prelucrarea” noastră nu poate accepta aşa ceva)

Sunt de făcut nişte corecturi: în loc de "Nume Prenume" pentru profesori, s-a înregistrat "Prenume Nume" (din fericire, la toţi profesorii) – ceea ce este graţios, dar cam incorect faţă de ordonarea uzuală după "Nume"; în loc de "XI I E" etc. trebuie să punem "XII E" etc. – sau şi mai bine "12E" etc. (dar nu "XIIE", că se alătură litere): orice prelucrare am avea de făcut, ea va putea fi mai uşor formulată într-un limbaj sau altul, dacă desemnăm clasele prin cifre arabe urmate imediat de o literă care să identifice clasa pe nivelul respectiv.

Ideea de „ordonare” din fişierul iniţial XLSX nu-i rea: profesorii fuseseră grupaţi din capul locului după disciplinele de învăţământ, înregistrând întâi directorii şcolii, apoi profesorii de pe obiectul "RO" (adică „Română”), apoi pe cei de la "FR", "EN", etc.; doar că ne încăpăţânăm să nu fim de acord cu folosirea unor prescurtări ca "MATE", sau "BAC", sau "profa de mate"… Pe de altă parte, există o „disciplină” notată prin „DE” (adică „Discipline Economice”) pe care sunt mulţi profesori şi care de fapt, acoperă o groază de alte discipline (acum câţiva ani erau vreo 50, cea mai interesantă fiind „Etalarea mărfurilor pe rafturi”); aproape o treime din orele unei clase ar apărea în orar ca "DE" – corect ar fi să se specifice despre care disciplină "DE" este vorba în ora respectivă.

Orarul unei şcoli este o oglindă, reflectând activitatea principală desfăşurată în acea şcoală şi el trebuie formulat decent, cu grijă faţă de normele uzuale de limbă şi de prezentare a datelor, evitând în orice caz exprimările neglijente şi nepăsătoare de pe FaceBook-uri; în particular, ordonarea trebuie făcută după nume, nu după funcţii şi nici după discipline (unde vei trece un profesor care predă cu tot dreptul, două sau trei discipline?), iar numele profesorilor trebuie scrise corect (folosind după caz, „diacritice”).

Corectarea, structurarea şi exploatarea datelor

R ne oferă (şi gratuit, exceptând timpul necesar instruirii prealabile) modalităţi uşor de folosit, eficiente şi foarte diverse, pentru prelucrarea datelor (dar nu înseamnă că excludem folosirea şi a altor limbaje). Desigur… gratuitatea este dezavantajoasă pentru decidenţii noştri, nefiind comisionabilă; în plus, te obligă la o instruire proprie anevoioasă, neîncorporabilă în „proiectele europene” aducătoare de „bani europeni”.

În vederea structurării ca "data.frame" a datelor din fişierul cear.csv, reformulăm întâi antetul (iniţial, acesta era format din două linii, consemnând zilele săptămânii şi respectiv, orele 1..12 pe fiecare zi), folosind următoarea secvenţă de program Python:

## antetul original (omiţând titlul tabelului, de pe primul rând):
## ,,Luni,,,,,,,,,,,,Marţi,,,,,,,,,,,,Miercuri,,,,,,,,,,,,Joi,,,,,,,,,,,,Vineri,,,,,,,,,,,
## ,,1,2,3,4,5,6,7,8,9,10,11,12,1,...,12,1,...,12,1,...12,1,...12
zile = ["l", "m", "c", "j", "v"]
ore = range(1, 13)
hdr = [z + str(o) for z in zile for o in ore]  # l1,l2,...,l12, m1,...m12, c1,...
print("ob, prof, " + ", ".join(hdr))

Şirul rezultat astfel pe ecran este "ob, prof, l1, l2, ..., l12, m1, ..., m12, ..." şi îl înscriem în locul primelor două linii din cear.csv; în antetul original, primele două coloane ale tabelului nu erau denumite – le-am numit acum, "ob" (coloana conţine obiectul asociat fiecărui profesor) şi "prof" (numele profesorilor).

Prin programul R următor, structurăm ca data.frame datele din fişierul cear.csv şi facem corecturile pe care le-am justificat mai sus: eliminăm coloana "ob" (fiindcă reprezintă obiectele incomplet şi prin denumiri neadecvate); modificăm "Pren Nume" în "Nume Pren"; eliminăm spaţiile existente în denumirile claselor şi înlocuim notaţia cu cifre romane prin cea arabică ("XI I E" ==> "12E"). În final, ordonăm alfabetic liniile din data.frame, după valorile din coloana "prof" şi salvăm din nou ca date textuale obişnuite, în fişierul qar.csv:

    ## transformă "cear.csv" într-o structură de date internă ("data.frame")
cear <- read.csv("cear.csv", stringsAsFactors=FALSE)
cear$ob <- NULL  # liniile din XLSX începeau cu "MATE", "DE" etc. (anulăm!)
    ## pentru ordonare după Nume (în XLSX apărea "Pren Nume")
prof <- cear$prof
l1 <- strsplit(prof, ' ')
first <- sapply(l1, function(x) x[1])
last <- sapply(l1, function(x) x[2])
cear$prof <- paste(last, first, sep=" ") # "Alina Stroe" devine acum "Stroe Alina", etc.
    ## "XI I B" etc. ==> "XIIB" (fără spaţii) ==> "12B" etc.
cear[-1] <- lapply(cear[-1], gsub, pattern=" ", replacement="", fixed=TRUE)
cear[-1] <- lapply(cear[-1], gsub, pattern="XII", replacement="12", fixed=TRUE)
cear[-1] <- lapply(cear[-1], gsub, pattern="XI", replacement="11", fixed=TRUE)
cear[-1] <- lapply(cear[-1], gsub, pattern="IX", replacement="9", fixed=TRUE)
cear[-1] <- lapply(cear[-1], gsub, pattern="X", replacement="10", fixed=TRUE)
    ## ordonăm alfabetic după coloana 'prof' şi salvăm datele în "qar.csv"
cear <- cear[order(cear$prof), ]
write.csv(cear, file="qar.csv", row.names=FALSE, quote=FALSE)

Pe structura de date care s-ar obţine prin read.csv() din fişierul qar.csv, vom putea face relativ uşor, prin secvenţe de program R, orice prelucrări asupra datelor orarului. De exemplu, să obţinem orarul zilei de Luni, într-o sesiune de lucru interactiv:

vb@Home:~/20LAR$ R -q  # deschide o sesiune ineractivă de lucru în R
> cear <- read.csv("qar.csv", stringsAsFactors=FALSE)
> cear[cear == ""] <- '.'  # înlocuieşte toate orele libere cu '.'
> cear[, (2:13)]  # orarul zilei de Luni (coloanele l1..l12)
    l1  l2  l3  l4  l5  l6  l7  l8  l9 l10 l11 l12
1  12A 12G   . 12E   .   .   .   .   .   .   .   .
2    .   .   .   .   .   .   .   .   .   .   .   .  # liber
3    .   .   .   . 12B   .  9G   . 10E  9F   .   .
4    .   .   .   .   . 11G   .   .   .   . 10B  9G
5  11C   . 11D 12F   .   .   .   .   .   .   .   .
6    . 12E   .   . 11G   .   .   . 10D   .  9B   .
7    .   .   . 12G   . 12D 10E   .   . 10D 10E   .
8    .   .   .   .   .   .   .   . 10F   .   .   .
9    .   .   .   .   .   .   . 10C   .   .   . 10B
10   .   .   .   .   .   .   .   .   .   .   .   .  # liber
11 12B   .   .   .   .   . 11F 10F 10G   .  9A   .
12   .   .   .   .   .   .   .   .   .   .   .   .  # liber
13 12C   . 11B   .   .   . 10A   .   .  9A   . 10C  # 4 ferestre (dar nu 7)
14 12E 12C   .   .   .   .  9A   .   .   .   .  9D
15   .   .   .   . 12E 12E   .   .  9F   .  9F   .
16   .   .   .   .   .   .   .  9E  9E   .   .   .
17   .   .   .   .   .   .   .   .   .   .   .   .  # liber
18   .   . 12E   . 11A   .   .   .   . 10E  9E   .
19   .   .   .   .   .   .   .   .   .   .   .   .  # liber
20 12G   .   . 11D 12G   . 10F   .  9D 10F 10D   .
21 12F   .   .   .   .   .   .   .   .   . 10G 10G
22   .   .   .   .   .   . 10C   .   .   .   .   .
23   .   . 11F   .   .   .  9E   .   .   .   .   .
24   . 11A   .   . 12A   . 11B  9D 10A   .   .   .
25   .   .   .   .   .   .   .   .   .   .   .   .  # liber
26   .   .   . 12A 11B   . 10B   .   .   .   .   .
27   .   .   .   .   .   .   .  9G   .   . 10C 10E
28   .   .   .   .   . 12A   .   .   .   .   .   .
29   .   .   . 11A   .   . 11A 10A   . 10B   .  9A
30 12D   .   .   .   .   .   .  9F 10B  9E   .   .
31   .   .   .   . 11C   .   . 10B  9B   .   .   .
32 11G 11D 12D   . 12F   .   . 10G   .   .   .   .
33 11D   .   .   .   . 11A   .   .   .  9C   .   .
34   .   .   .   .   .   .   .   .   .   .   .   .  # liber
35   .   .   . 12C 11E 11F   .   .   . 10C   .   .
36   . 12A 12G   . 12D   .   .   .   .   .   .   .
37   . 11F   .   .   .   .   .  9B  9A 10G   .   .
38   .   . 12B 11B   .   .   .   . 10C   . 10F   .
39   . 12D   .   . 11D 12F   .   .   .   .   .   .
40 11A   . 12C 11C   .   .   .   .   .  9B   .  9F
41   .   .   . 11A   .   .  9B   .   .   .   .   .
42   .   . 12A 12B   . 11B   .  9A   .   .   .   .
43 11F 11E 11E 11F   .   .   .   .   .   .   .   .
44   .   .   .   .   .   .   .   .   .   . 10A  9E
45   .   .   .   .   .   .   .   .   .   .   .   .  # liber
46   .   .   .   .   .   . 10D   .  9G  9D   . 10D
47 11B   . 11C 12D   .   .  9C  9C   .   .  9D   .
48   .   . 12F   .   .   .   . 10D  9C 10A   .   .
49   .   .   . 11E 12C   . 10G   .   .  9G   .   .
50   . 12F   .   .   .   .   .   .   .   .   . 10A
51   .   .   .   .   .   .  9F   .   .   .   .   .
52   .   .   .   .   .   .   .   .   .   .   .   .  # liber
53   .   .   .   .   .   .   .   .   .   .   .   .  # liber
54   . 11G 11G 11G   .   .   .   .   .   .  9G   .
55   . 11B   .   .   .   .   .   .   .   .   .   .
56   . 12B 11A   .   .   .  9D 10E   .   .  9C 10F
57   . 11C   .   . 11F 11E   .   .   .   .   .   .
>

În loc de numele profesorilor, avem indecşii 1..57 ai liniilor corespunzătoare lor (pentru a nu mai disputa asupra reglementărilor europene privitoare la „datele personale”); nu-i frumos, dar nici nu trebuie. De observat că 10 profesori sunt liberi în ziua respectivă; exceptând profesorul 43 (şi pe cei cu câte o singură oră, 8, 22 şi 51), toţi ceilalţi au câte cel puţin o fereastră (în sensul din [1]; de exemplu, 56 are două ferestre – anume, în schimbul al doilea, cuprinzând orele 7..12 din zi – şi nu cinci); în total, sunt 60 de ferestre.

Dacă acum, la prompt-ul liniei curente din consola R introducem:

> write.csv(cear[, (1:13)], file="luni.csv", row.names=FALSE, quote=FALSE)

atunci obţinem fişierul "luni.csv", conţinând orarul şcolii pe ziua de Luni (incluzând acum şi numele profesorilor); eliminând antetul, putem „pasta” acest fişier în aplicaţia din [2] şi operând acolo, putem reduce numărul de ferestre. Bineînţeles că este convenabil să eliminăm în prealabil, cele 10 linii corespunzătoare profesorilor liberi; dacă nu le-am fi eliminat, ar fi fost mai incomod de observat ce interschimbări de clase trebuie făcute pentru a scăpa de ferestre — dar pe de altă parte, păstrând în fiecare zi şi liniile libere, în loc să le eliminăm, nici n-am mai fi avut „problema cu liste” (recompunerea orarului săptămânal din noile orare zilnice) de la care am plecat aici.

Dar… ne-am grăbit: degeaba „pastăm” luni.csv în aplicaţia din [2] – nu funcţionează, pentru că în [2] este statuat ca orele libere să fie marcate nu cu '.', ci cu '-' pentru schimbul întâi şi cu '~' pentru schimbul al II-lea din zi. Pentru a îndrepta lucrurile, cel mai bine este să revenim în consola R şi să reconstruim luni.csv:

> luni <- cear[, (1:13)]
> sch1 <- luni[, (2:7)]  # orele din schimbul 1
> sch1[sch1 == '.'] <- '-'
> sch2 <- luni[, (8:13)]  # orele din schimbul 2
> sch2[sch2 == '.'] <- '~'
> luni[, (2:7)] <- sch1
> luni[, (8:13)] <- sch2
> write.csv(luni, file="luni.csv", row.names=FALSE, quote=FALSE)

Noul luni.csv respectă acum cerinţele din [2] şi poate fi pastat acolo (eliminând totuşi linia de antet, precum şi liniile profesorilor liberi); este important de păstrat şi numele profesorilor (nu doar indecşii corespunzători), fiindcă interschimbând orele (folosind operaţia swap din [2]), trebuie să ţinem seama de nişte „metainformaţii” care au suplimentat verbal tabelul XLSX furnizat iniţial, de genul: "profesorul cutare trebuie să aibă ore numai în intervalul orar 9-12".

Folosind de un anumit număr de ori acţiunea "swap" de pe aplicaţia interactivă din [2], am reuşit (în vreo 20 de minute) să reducem numărul iniţial (60) de ferestre la 7; din [2], prin "Export" avem fişierul "ret_orar.csv", pe care îl putem exploata iarăşi din R (dar iarăşi ne ferim aici, de coloana numelor profesorilor):

> luni <- read.csv("ret_orar.csv", stringsAsFactors=FALSE)
> luni[, (2:13)]  # fără numele profesorilor
    l1  l2  l3  l4  l5  l6  l7  l8  l9 l10 l11 l12
1    - 12A 12E 12G   -   -   ~   ~   ~   ~   ~   ~
2    -   -   -   - 12B   -  9G  9F 10E   ~   ~   ~
3    -   -   -   -   - 11G   ~   ~   ~  9G 10B   ~
4    - 12F 11C 11D   -   -   ~   ~   ~   ~   ~   ~
5  11G 12E   -   -   -   -   ~   ~   ~  9B 10D   ~
6    -   -   -   - 12G 12D 10D   ~   ~   ~ 10E 10E
7    -   -   -   -   -   -   ~   ~ 10F   ~   ~   ~
8    -   -   -   -   -   - 10B 10C   ~   ~   ~   ~
9  12B   -   -   -   -   - 11F 10F 10G  9A   ~   ~
10 12C 11B   -   -   -   - 10A  9A 10C   ~   ~   ~
11 12E 12C   -   -   -   -   ~   ~   ~   ~  9D  9A
12   -   -   -   - 12E 12E   ~   ~   ~   ~  9F  9F
13   -   -   -   -   -   -   ~   ~  9E  9E   ~   ~
14   -   -   - 12E 11A   - 10E  9E   ~   ~   ~   ~
15 12G 12G 11D   -   -   -   ~   ~ 10D 10F 10F  9D
16   -   -   -   - 12F   -   ~   ~   ~   ~ 10G 10G
17   -   -   -   -   -   -   ~   ~   ~ 10C   ~   ~
18   -   -   -   - 11F   -  9E   ~   ~   ~   ~   ~
19   -   -   - 11A 12A   - 11B  9D 10A   ~   ~   ~
20   -   -   - 12A 11B   -   ~   ~   ~   ~   ~ 10B
21   -   -   -   -   -   -   ~   ~   ~ 10E  9G 10C
22   -   -   -   -   - 12A   ~   ~   ~   ~   ~   ~
23   -   -   -   -   - 11A 11A 10A  9A 10B   ~   ~
24 12D   -   -   -   -   -   ~   ~ 10B  9F  9E   ~
25   -   -   -   - 11C   -  9B 10B   ~   ~   ~   ~
26 11D 11G 12D 12F   -   -   ~ 10G   ~   ~   ~   ~
27   - 11D 11A   -   -   -   ~   ~   ~  9C   ~   ~
28   -   -   - 12C 11E 11F 10C   ~   ~   ~   ~   ~
29 12A 12D 12G   -   -   -   ~   ~   ~   ~   ~   ~
30   - 11F   -   -   -   -   ~   ~  9B 10G  9A   ~
31   -   - 12B 11B   -   -   ~   ~   ~   ~ 10C 10F
32   -   -   - 12D 11D 12F   ~   ~   ~   ~   ~   ~
33 11C 11A 12C   -   -   -   ~  9B  9F   ~   ~   ~
34   -   - 12A 12B   - 11B  9A   ~   ~   ~   ~   ~
35 11F 11E 11E 11F   -   -   ~   ~   ~   ~   ~   ~
36   -   -   -   -   -   -   ~   ~   ~   ~ 10A  9E
37   -   -   -   -   -   -  9D 10D  9G 10D   ~   ~
38   -   - 11B 11C 12D   -  9C  9C  9D   ~   ~   ~
39 12F   -   -   -   -   -   ~   ~   ~ 10A  9C 10D
40   -   -   -   - 12C 11E 10G  9G   ~   ~   ~   ~
41   -   - 12F   -   -   -   ~   ~   ~   ~   ~ 10A
42   -   -   -   -   -   -  9F   ~   ~   ~   ~   ~
43   -   - 11G 11G 11G   -   ~   ~   ~   ~   ~  9G
44 11B   -   -   -   -   -   ~   ~   ~   ~   ~   ~
45 11A 12B   -   -   -   - 10F 10E  9C  9D   ~   ~
46   - 11C 11F 11E   -   -   ~   ~   ~   ~   ~   ~

Au rămas numai 46 dintre cei 57, fiindcă pe lângă cei 10 care nu au ore lunea, am mai eliminat un profesor: există două-trei clase la care trebuie să intre doi profesori simultan şi am păstrat numai unul dintre aceştia.

Desigur, sunt fel de fel de observaţii de făcut; de exemplu, 12B are numai 5 ore şi pe linia 9 am putea muta clasa respectivă din coloana "l1" în coloana "l6" (scutind profesorul respectiv de a veni de două ori la şcoală) – numai că indicaţia era ca dacă o clasă are mai puţin de 6 ore, atunci ora liberă să fie ultima oră din schimbul respectiv. Văzând şi orarele pe celelalte zile, a devenit clar că orele au fost repartizate dezechilibrat: există clase cu 5 ore într-o zi şi cu 7 ore într-o altă zi; există profesori cu 2 ore într-o zi şi cu 7 ore sau şi mai mult, într-o altă zi.

Procedând şi pentru celelalte zile ca în cazul redat mai sus al zilei de Luni, am obţinut cele 5 orare zilnice, fiecare cu mult mai puţine ferestre decât existau iniţial. Problema care s-ar pune acum, constă în strângerea orarelor zilnice obţinute ca fişiere CSV, într-un fişier CSV similar fişierului iniţial "cear.csv" (de exemplu, pentru a reda orarul săptămânal în Excel cum am văzut că este obiceiul).

De data aceasta am preferat să lucrăm direct cu fişierele CSV (în loc să le structurăm ca data.frame în R), printr-un program Python; înfiinţăm un dicţionar "Qor{}", care va avea drept chei numele profesorilor şi drept valori, câte o listă conţinând ca subliste, cele cinci orare zilnice ale profesorului respectiv; cheile din "Qor" sunt preluate din fişierul "adj.csv", în care am copiat dar fără antet, fişierul iniţial "cear.csv"; dacă profesorul nu are ore în ziua respectivă, atunci prin funcţia "cmpl()" înscriem în sublista orelor din acea zi, secvenţa de "-" şi "~" corespunzătoare acestei situaţii:

Qor = {}  # Prof: [[cvs_1], [cvs_2], ..., [cvs_5]]
          # exemplu: cvs_1 = '11A,12B,-,-,-,-,10F,10E,9C,9D,~,~'

def cmpl(n):  # completează [cvs_n], dacă Prof NU are ore în Zi_n
    for prof in Qor:
        if len(Qor[prof]) < n:
            Qor[prof].append(['-,-,-,-,-,-,~,~,~,~,~,~'])

with open("adj.csv") as adj:  # adj.csv conţine orarul (Prof,11B,12D,...)
    for row in adj:  # fixează Prof drept chei ale dicţionarului Qor
        Qor[row.split(",", 1)[0]] = []

def addInQor(csvz, zi):  # înscrie în Qor orele din fişierul CSV al zilei
    with open(csvz) as csv:
        for row in csv:
            pror = row.split(",", 1)
            prof = pror[0]
            Qor[prof].append([pror[1]])
    cmpl(zi)  # completează [cvs_n] la Prof fără ore în acea zi

zi = 1
for csv in ["luni1.csv", "marti1.csv", "mier1.csv", "joi1.csv", "vin1.csv"]:
    addInQor(csv, zi)
    zi += 1

# înscrie datele reunite în Qor, într-un fişier CSV
fo = open("adj2.csv", "w")
for k,e in Qor.items():
    fo.write(k)
    for l in e:
        fo.write("," + "".join(l).rstrip())
    fo.write('\n')
fo.close()    

Fişierul rezultat "adj2.csv" recompune orarul săptămânal, din orarele zilnice date; dacă îi adăugăm la început antetul din fişierul XLSX iniţial, obţinem noua formulare a orarului şcolii (cu mai puţine ferestre), ca fişier CSV – care (probabil, marea noutate) poate fi deschis direct şi din Excel.

Cea mai simplă soluţie

Ar fi fost mult mai simplu să „recompunem” orarul, lucrând în R; dar… cine să vadă cea mai simplă soluţie, la o problemă sau alta?!

În programul Python redat mai sus ne-am bazat pe tipul de date „dicţionar”, asociind profesorii cu orarele zilnice aferente; în R nu dispunem în mod explicit de „dicţionar”, dar… putem simula aşa ceva.

Un obiect R de tip data.frame are asociate „nume” prin care se identifică liniile şi coloanele; „numele” care au fost asociate în mod implicit liniilor din cear (data.frame în care preluasem datele din orarul qar.csv) sunt indicii de linii 1..57; „numele” asociate în mod implicit liniilor din luni (data.frame în care preluasem datele din orarul pe Luni, ret_orar.csv) sunt indicii 1..46 ai liniilor respective; fiindcă în luni păstrasem numai acei profesori care au ore în ziua de Luni, indicii 1..57 şi respectiv 1..46 nu referă neapărat, aceiaşi profesori.

Însă valorile din coloana luni$prof se regăsesc în coloana cear$prof; prin urmare, dacă am înlocui „numele” 1..57 şi respectiv 1..46 chiar cu numele profesorilor, din coloanele prof ale celor două obiecte data.frame – atunci un acelaşi „nume” ar referi liniile corespunzătoare unui aceluiaşi profesor, în cele două obiecte data.frame.

Setăm noile „nume” de linii folosind funcţia rownames():

> rnC <- rownames(cear) <- cear$prof
> rnL <- rownames(luni) <- luni$prof

Nu rămâne decât să selectăm din cear numai profesorii indicaţi în rnL şi să înscriem acestora (în coloanele 2..13) valorile corespunzătoare lor din luni:

> cear[rnL, (2:13)] <- luni[, (2:13)]

Pentru profesorii care au rămas liberi (se află în rnC, dar nu în rnL), putem înregistra o secvenţă de '.' (fără să mai disociem între cele două schimburi):

> cear[rnc[!rnc %in% rnl], (2:13)] <- rep('.', 12)

Preluând în luni datele din "marti1.csv" (apoi, din "mier1.csv, etc.) şi repetând secvenţa celor patru comenzi de mai sus (în care însă, trebuie să ţinem seama că în cear orele de marţi sunt în coloanele 14:25, cele de miercuri în coloanele 26:37, etc.) – vom obţine în cear recompunerea dorită a orarelor zilnice (şi prin write.csv() putem regăsi fişierul "adj2.csv" obţinut anterior prin programul Python redat mai sus).

O problemă de înfiinţare

Este foarte de înţeles că profesorii nu agreează ferestrele: în şcoală nu prea există condiţii pentru a te folosi în mod util de „ferestre”; cel mai convenabil ar fi să vii o singură dată la şcoală (nu o dată pentru orele din primul schimb şi încă o dată, pentru al doilea schimb) şi să-ţi faci orele fără nicio fereastră.

Cum am putea „repara” situaţiile în care ai o singură oră într-un schimb şi mai multe ore depărtate în celălalt schimb al unei zile (cazul profesorilor 9, 24, 27, 30, 39, 43 din orarul pe Luni listat mai sus)? O idee ar fi de a angaja în aplicaţia din [2] nu doar orarul zilei curente, dar şi orarul unei alte zile – instanţiind de două ori widget-ul respectiv (de exemplu pentru luni.csv şi pentru joi.csv), urmând să modelăm încă o operaţie swap, prin care să putem interschimba ore între cele două widget-uri. De exemplu, dacă joi clasa 12B are 7 ore, atunci am putea muta una dintre acestea în ziua de Luni (unde 12B avea numai 5 ore); clasa 12D de la profesorul 24 din ziua de Luni ar putea fi mutată eventual joia, aducând în loc la un alt profesor un 12D din ziua de joi; ş.a.m.d.

Problema înfiinţată de această idee constă în a defini o acţiune swap12 prin care ora specificată pentru un anumit profesor din primul widget să fie transferată în al doilea widget, păstrând „integritatea” celor două orare zilnice (să nu se piardă vreo oră, să nu apară vreuna în plus şi bineînţeles, să nu intre doi profesori simultan la o aceeaşi clasă).

Suplimentată cu swap12, aplicaţia interactivă din [2] ar permite o mai bună „reparare” a ferestrelor dintr-un orar existent (dar swap12 aşteaptă încă să fie înfiinţată).

docerpro | Prev |