Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ===== Tema 1 PP – Barcode decoder ===== Publicare: ''24 martie 2025''\\ Deadline: ''9 aprilie 2025'' Schelet de cod: {{:pp:2025:scala:tema1_pp_2025.zip|}} După ce ați descărcat scheletul, creați un proiect nou din IntelliJ în Scala, în care copiați folderele și fișierul din arhivă în root-ul proiectului (folderul ''src'' va fi suprascris). Dați restart la IDE și ar trebui să funcționeze. <note warning>După deadline fiecare student va prezenta tema la laborator, explicând în detaliu implementarea.</note> <note important>În soluție nu aveți voie să programați cu efecte laterale. Folosiți declarații doar ''val'', nu <del>var</del>.</note> ===== Ce este un cod de bare? ===== Un cod de bare este o reprezentare vizuală a datelor, ușor de citit de dispozitive. De obicei datele descriu proprietăți ale obiectului pe care se află codul de bare. Codurile de bare tradiționale reprezintă datele prin varierea lățimilor liniilor și spațiilor paralele. Această reprezentare este lineară sau 1D. Există și reprezentări 2D, cum ar fi codurile QR, care au o logică de matrice și pot codifica mai multe date. Un cod de bare este un număr reprezentat printr-o succesiune de bare negre (bare) și bare albe (spații). De obicei barele negre codifică biți de **1**, iar barele albe biți de **0**. **Mai mulți biți alăturați pot crea o bară mai groasă**. În imaginea de mai jos puteți vedea codul de bare pentru cifrele 5901234123457, după standardul European Article Number(EAN-13).\\ {{https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/EAN-13-5901234123457.svg/330px-EAN-13-5901234123457.svg.png}} Codurile de bare descriu 13 cifre, însă doar 12 cifre sunt codificate efectiv deoarece prima cifră este codificată indirect. Fiecare cifra fiind codificată folosind ''4 bare'' de lățimi diferite. Pe lângă barele care codifică cifre, există și o secvență de start de ''3 bare'', una de stop de ''3 bare'' și una de centru de ''5 bare'' (barele acestor secvențe speciale sunt mai lungi). În total sunt ''59 de bare (12 * 4 + 3 + 3 + 5)''.\\ Puteți vedea mai multe în această [[https://en.wikipedia.org/wiki/International_Article_Number#Composition | secțiune]]. Secvența de 59 de bare conține 95 de biți. De la stânga la dreapta aceștia sunt organizați astfel: - 3 biți **101** pentru a marca startul - 42 biți (7 pentru fiecare cifră) pentru cifrele de pe pozițiile 2->7 (prima cifră este codificată indirect) - 5 biți **01010** pentru a marca centrul - 42 biți (7 pentru fiecare cifră) pentru cifrele de pe pozițiile 8->13 - 3 biți **101** pentru a marca sfârșitul === Codificarea cifrelor === Vom considera cifrele numerotate de la stânga la dreapta după poziție, începând de la poziția 1.\\ Cifrele codului de bare sunt împărțite în 3 părți: * Prima cifră, numită cifră de paritate * Primul grup este reprezentat de următoarele 6 cifre, de pe pozițiile 2->7 * Al doilea grup este reprezentat de ultimele 6 cifre, de pe pozițiile 8->13 Spunem despre codificarea unei cifre că e de paritate ''pară'' dacă conține un număr par de biți de 1. Altfel e ''impară''. Pentru fiecare cifră din primul grup (pozițiile 2->7) există două codificări posibile, una cu un număr impar de biți de 1 (codificare **L**, paritate ''impară'') și una cu un numar par de biți de 1 (codificare **G**, paritate ''pară''). Pentru fiecare cifră din al doilea grup (pozițiile 8->13) există o singură codificare posibilă (codificare **R** paritate ''pară'' ). ^ Cifra ^ Codificare-L ^ Codificare-G ^ Codificare-R^ | **0** | **0001101** | **0100111** | **1110010**| | **1** | **0011001** | **0110011** | **1100110**| | **2** | **0010011** | **0011011** | **1101100**| | **3** | **0111101** | **0100001** | **1000010**| | **4** | **0100011** | **0011101** | **1011100**| | **5** | **0110001** | **0111001** | **1001110**| | **6** | **0101111** | **0000101** | **1010000**| | **7** | **0111011** | **0010001** | **1000100**| | **8** | **0110111** | **0001001** | **1001000**| | **9** | **0001011** | **0010111** | **1110100**| Observații: * Codul de bare începe cu o cifră codificată ''impar'' și se termină cu o cifră codificată ''par'', astfel scannerele pentru codurile de bare pot determina orientarea și să citească și de la stânga la dreapta, dar și invers. * Codificările-R sunt complementele pe biți ale codificărilor-L. * Codificările-G sunt inversele codificărilor-R. * Codificările G și L încep cu **0** și se termină cu **1**. Codificările R încep cu **1** și se termina cu **0**. Astfel, fiecare cifră va fi reprezentată prin 4 bare alternante de grosimi diferite. * Grosimea maximă a unei bare va fi 4. Barele a două cifre nu se amestecă. === Cifra de paritate === După cum am spus anterior, prima cifră se numește cifră de paritate. Cifra de paritate ''nu'' este reprezentată direct printr-o succesiune de bare și spații, ci este codificată indirect, prin alegerea unei combinații de moduri de codificare L sau G pentru primul grup de 6 cifre, conform tabelului de mai jos. Practic, este suficient să știm ce codificare a fost folosită pentru fiecare dintre cele 6 cifre din primul grup pentru a determina cifra de paritate asociată. Dacă combinația găsită nu este asociată cu o cifră din tabelul de mai jos codul este invalid. ^ Cifra de paritate ^ Primul grup ^ | **0** | **LLLLLL**| | **1** | **LLGLGG**| | **2** | **LLGGLG**| | **3** | **LLGGGL**| | **4** | **LGLLGG**| | **5** | **LGGLLG**| | **6** | **LGGGLL**| | **7** | **LGLGLG**| | **8** | **LGLGGL**| | **9** | **LGGLGL**| === Calculare cifră de control === Ultima cifră a unui cod de bare EAN-13 se numeste cifră de control. Este folosită pentru a confirma citirea corectă a unui cod. Fiecare cifră din codul de bare (fară cea de control), are o pondere de 1 sau 3 în funcție de poziție la calculul cifrei de control (cele de pe poziții impare au pondere 1, iar cele de pe poziții pare au pondere 3, vezi tabelul de mai jos). | Poziție | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | | Pondere | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | Formula pentru cifra de control este următoarea: $math[C = \left(10 - \left( \sum_{i=1}^{12} w_i \cdot c_i \right) \mod 10 \right) \mod 10] Unde $math[w_i] este ponderea cifrei de pe poziția $math[i] și $math[c_i] este cifra de pe poziția $math[i]. === Exemplu === Să revenim la imaginea pentru codul de bare 5901234123457 din introducere.\\ {{https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/EAN-13-5901234123457.svg/330px-EAN-13-5901234123457.svg.png}} \\ Reprezentarea binară a acestor bare este următoarea: 10100010110100111011001100100110111101001110101010110011011011001000010101110010011101000100101\\ Prima cifră, adică cifra de paritate, are valoarea 5. Conform tabelului de mai sus, asta va duce la codificarea următoarelor 6 cifre în formatul **LGGLLG**. Ultimele 6 cifre sunt codificate mereu în formatul **RRRRRR**. | Cifra | start | 9| 0 | 1 | 2 | 3 | 4 | centru | 1 | 2 | 3 | 4 | 5 | 7 | end | | Codificare | - | L| G | G | L | L | G | - | R | R | R | R | R | R | - | | Reprezentare | 101 | 0001011| 0100111 | 0110011 | 0010011 | 0111101 | 0011101 | 01010 | 1100110 | 1101100 | 1000010 | 1011100 | 1001110 | 1000100 | 101 | Acum să verificăm dacă cifra de control 7 este corectă. | Poziție | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | | Pondere | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | 1 | 3 | | Cifra | 5 | 9 | 0 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4| 5 | Suma produselor dintre cifre si ponderile asociate este 83. Cifra de contol este (10 - (83 % 10)) % 10 = 7, deci este corectă. ====== Implementare ====== Avem imagini cu codurile de bare (în format **.ppm**) ale unor produse care trebuie cumpărate. Veți implementa un decodificator simplificat de coduri de bare. Cu ajutorul acestuia magazinele obțin numărul EAN-13 si cu ajutorul bazei de date identifică produsul vândut și calculează prețul.\\ Veți primi codul pentru parsarea si prelucrarea de imagini, care face trecerea de la formatul color **.ppm** la formatul alb-negru **.pbm**, voi fiind nevoiți să implementați doar logica unui decodificator care primește o matrice de pixeli de **0** și de **1**. Această matrice corespunde unei imagini alb-negru, fiecare pixel **negru** având valoarea **1** și fiecare pixel **alb** având valoarea **0**.\\ Vom identifica cifrele bazându-ne pe grosimile relative ale barelor față de o unitate elementară, adică față de o bară care codifică un singur bit. __Veți rezolva TODO-urile din fișierul **Decoder**.__ <color #ed1c24>Nu este nevoie să faceți nimic cu celelalte fișiere, ele v-au fost puse la dispoziție doar în scop didactic.</color> **Notă**: Pentru vizualizarea imaginilor .ppm pe WSL/Linux puteți folosi: <code bash> sudo apt install feh feh image.ppm </code> === Algoritm === Primim o matrice de biți 0 și 1, reprezentând imaginea în formatul alb-negru descris anterior. Decupăm din matricea de pixeli rândurile din mijloc și rulăm algoritmul de identificare a barelor pe toate aceste rânduri, pentru a avea șanse mai mari de succes. Vrem să găsim barele din imagine, așa că grupăm biții identici și consecutivi în tupluri de tipul **(<număr_repetări>, <bit>)**, unde tuplul descrie grosimea și culoarea barei din imagine. Va rezulta următorul comportament:\\ <nowiki> 0001111111010001100011111 => [(3, 0), (7, 1), (1, 0), (1, 1), (3, 0), (2, 1), (3, 0), (5, 1)] </nowiki> \\ Căutăm o secvență de 59 de bare pentru fiecare rând din imagine în care identificăm secvențele de start și de stop.\\ Secvențele de 59 de bare care conțin secvențele de start și stop din fiecare rând sunt returnate de funcția ''checkRow'', a cărei implementare este dată de echipă.\\ Din secvența de 59 de bare extragem grupuri de 4 bare care reprezintă codificarea unei cifre (după ce am eliminat secvențele de bare care marchează start, centru și sfârșit). Pentru primele 6 cifre găsim codificarea cea mai apropiată L sau G, iar pentru ultimele 6 cifre găsim cea mai apropiată codificare R. Pentru a identifica cifra codificată de un set de 4 bare vom calcula distanța dintre secvența de bare din poză și secvențele de bare pentru codificările L, G sau R. Vom alege codificarea cea mai apropiată (de distanță minimă) pentru fiecare din cele 12 cifre. De la formatul descris pentru bare vom lua grupe de câte 4 bare care reprezintă o cifră și vom trece la formatul dimensiunilor relative ale segmentelor, impărțind numărul de aparitii la lungimea totală.\\ <nowiki> [(3, 0), (7, 1), (1, 0), (1, 1)] => [(3/12, 0), (7/12, 1), (1/12, 0), (1/12, 1)] </nowiki>\\ Pentru că biții alternează întotdeauna, vom reduce codificarea la un tuplu format din primul bit și dimensiunile relative ale fiecărei bare:\\ <nowiki> [(3/12, 0), (7/12, 1), (1/12, 0), (1/12, 1)] => (0, (3/12, 7/12, 1/12, 1/12)) </nowiki>\\ Având codificările standard ale cifrelor în același format, vom alege codificarea care începe cu același bit și pentru care distanța (suma modulelor diferențelor dintre rapoartele aflate pe aceeași poziție) este minimă, adică codificarea pentru care barele respectă proporții de grosime similare. După ce am identificat ultimele 12 cifre ale codului de bare, vom determina cifra de paritate. Pentru a determina cifra de paritate ne vom uita la paritatea codificărilor identificate pentru cele 6 cifre din primul grup. [[#cifra_de_paritate| vezi mai sus]]. După ce am determinat cifra de paritate, calculăm cifra de control și verificăm dacă este identică cu cea recunoscută din poză. [[#calculare_cifra_de_control| vezi mai sus]]. Dacă cifra de control este corectă, am identificat toate cifrele și întoarcem rezultatul. Altfel întoarcem None. <hidden Recapitulare functii de ordin superior> **map()** * Poate fi aplicată pe orice colecție (ex: ''List''). * Primește o funcție ca parametru și aplică funcția respectivă pe fiecare element al colecției. * Întoarce o colecție de același tip (dacă era o colecție de tip ''List'', rămâne tot ''List'' după aplicarea ''map()''). Exemplu: <code scala> val initialList : List[Int] = List(1, 2, 3, 5) // Folosind o funcție def func(x : Int) : Int = x * 2 val listAfterMapWithFunc: List[Int] = initialList.map(func) // List(2, 4, 6, 10) // Folosind lambda val listAfterMapWithLambda: List[Int] = initialList.map(x => x + 7) // List(8, 9, 10, 12) </code> **Notă:** Ultima variantă este echivalenta cu <code scala> initialList.map(_ + 7) </code> **zip()** * Este aplicată pe o colecție, primește ca parametru o altă colecție. * Întoarce o colecție de tupluri cu elementele din cele două colecții. Exemplu: <code scala> val firstList : List[Int] = List(3, 8, 7, 5) val secondList : List[String] = List("verde", "rosu", "albastru", "galben") val result : List[(Int, String)]= firstList.zip(secondList) // List((3,verde), (8,rosu), (7,albastru), (5,galben)) </code> **foldRight() & foldLeft()** * Operația de folding pe o listă în scala înseamnă aplicarea unei operații între un acumulator și fiecare element din listă, rezultatul fiind valoarea acumulatorului după parcurgerea întregii liste. **foldLeft(z: B)(op: (B, A) => B): B** * Primește un acumulator inițial și o funcție, întoarce un acumulator. * Funcția parametru primește un tuplu format dintre acumulator și un element al listei și întoarce un acumulator. * Aplică operația pe listă de la stânga la dreapta. <code scala> val list : List[Int] = List(1, 2, 3) // (((0 - 1) - 2) - 3) val result : Int = list.foldLeft(0)((acc, el) => acc - el) // -6 </code> **Notă:** Poate fi rescris ca <code scala> list.foldLeft(0)(_-_) </code> **foldRight(z: B)(op: (A, B) => B): B** * Primește un acumulator inițial și o funcție, întoarce un acumulator. * Funcția parametru primește un tuplu format dintre un element al listei și acumulator și întoarce un acumulator. * Aplică operația pe listă de la dreapta la stânga. Exemplu: <code scala> val list : List[Int] = List(1, 2, 3) // (1 - (2 - (3 - 0))) val result : Int = list.foldRight(0)((el, acc) => el - acc) // 2 </code> **Notă:** Poate fi rescris ca <code scala> list.foldRight(0)(_-_) </code> </hidden> ===== 1. Funcții elementare (20p) ===== 1.1. Avem nevoie de un TDA pentru biți. Realizați conversiile de la ''Char'' și ''Int'' la ''Bit''. <code scala> def toBit(s: Char): Bit = ??? def toBit(s: Int): Bit = ??? </code> ''Hint:'' Inspectați fișierul **Types**. 1.2. Implementați funcția care întoarce complementul unui bit. <code scala> def complement(c: Bit): Bit = ??? </code> 1.3. Pornind de la ''LStrings'' care este lista codificărilor L ca ''String'' (pe fiecare poziție gasim codificarea sa L), definiți listele pentru toate cele 3 tipuri de codificări. **Soluțiile hardcodate nu vor fi luate în considerare, trebuie să vă folosiți de LStrings sau de listele deja create (pentru rightOddList și leftEvenList) împreună cu funcții de ordin superior.** <code scala> val leftOddList: List[List[Bit]] = Nil // codificări L val rightList: List[List[Bit]] = Nil // codificări R val leftEvenList: List[List[Bit]] = Nil // codificări G </code> 1.4. Implementați funcția ''group'' care grupează elementele egale și consecutive dintr-o listă în liste separate. <code scala> def group[A](l: List[A]): List[List[A]] = ??? </code> ''Hint:'' **<nowiki>group([1, 1, 2, 2, 3, 3, 1]) = [[1, 1], [2, 2], [3, 3], [1]]</nowiki>** 1.5. Implementați funcția ''runLength'' care grupează elementele egale și consecutive dintr-o listă în elemente noi de tipul **(<număr_apariții>, <element>)**. <code scala> def runLength[A](l: List[A]): List[(Int, A)] = ??? </code> ''Hint:'' Folosiți ''group'' implementat anterior. ===== 2. Numere raționale (10p) ===== 2.1. Vrem să folosim tipul ''RatioInt'' pentru lucrul cu fracții. Implementați operațiile uzuale cu fracții în clasa ''RatioInt''. <code scala> def -(other: RatioInt): RatioInt = ??? def +(other: RatioInt): RatioInt = ??? def *(other: RatioInt): RatioInt = ??? def /(other: RatioInt): RatioInt = ??? </code> 2.2. Implementați funcția de comparare a doua fracții. Întoarce **-1** daca fracția e mai mică decât **other**, **1** dacă e mai mare și **0** dacă sunt egale. <code scala> def compare(other: RatioInt): Int = ??? </code> ===== 3. Transformarea inputului (30p) ===== 3.1. Implementați funcția care primește o lista de elemente de tipul **(<număr_apariții>, <element>)** și întoarce o listă cu elemente de tipul **(<frecvență relativă>, <element>)**, unde $math[\text{frecvență relativă}=\dfrac{\text{număr apariții}}{\text{număr total de elemente}}] , unde elementele sunt cele din lista inițială pe care am rulat ''runLength''. <code scala> def scaleToOne[A](l: List[(Int, A)]): List[(RatioInt, A)] = ??? </code> 3.2. Implementați funcția care primeste o listă de biți și întoarce un tuplu format din primul bit și o listă cu dimensiunile relative ale segmentelor de biți egali. <code scala> def scaledRunLength(l: List[(Int, Bit)]): (Bit, List[RatioInt]) = ??? </code> 3.3. Pornind de la un șir de caractere ce reprezintă parități, unde **G** înseamnă par, iar **L** impar, convertiți-l la ''List[Parity]''. <code scala> def toParities(s: Str): List[Parity] = ??? </code> 3.4. Creați lista de parițăți pe care o vom folosi pornind de la ''PStrings''. **Soluțiile hardcodate nu vor fi luate în considerare, trebuie să vă folosiți de PStrings** <code scala> val leftParityList: List[List[Parity]] = Nil </code> 3.5. Rulați ''scaledRunLength'' pe fiecare element din listele create la task-ul **1.3**. **Soluțiile hardcodate nu vor fi luate în considerare, trebuie să vă folosiți de listele create anterior.** <code scala> type SRL = (Bit, List[RatioInt]) val leftOddSRL: List[SRL] = Nil val leftEvenSRL: List[SRL] = Nil val rightSRL: List[SRL] = Nil </code> ''Hint:'' Folosiți și ''runLength''. ===== 4. Identificarea cifrelor (40p) ===== 4.1. Definiți funcția de distanță dintre două codificări, care primește tupluri cu primul bit, respectiv lungimile relative ale segmentelor unei codificări (tip definit ca ''SRL'') și întoarce suma modulelor diferențelor dintre rapoartele aflate pe aceeași poziție în ambele codificări. <code scala> def distance(l1: SRL, l2: SRL): RatioInt = ??? </code> ''Hint:'' Dacă codificările sunt alcătuite din bare complementare (■□■□ vs □■□■, unde □ reprezintă o bară de **0**, iar ■ o bară de **1**), puteți întoarce o distanță "infinită" (un număr foarte mare), de exemplu ''RatioInt(100, 1)'' ''Hint:'' Folosiți ''zip'' între listele primite. 4.2. Definiți funcția ''bestMatch'' care primește ca parametrii o listă de codificări L, G sau R în forma ''SRL'' și codificarea binară a unei cifre și găsește cea mai bună potrivire din listă. Întoarce un tuplu cu cea mai mică distanță și cifra găsită. <code scala> def bestMatch(SRL_Codes: List[SRL], digitCode: SRL): (RatioInt, Digit) = ??? </code> ''Hint:'' Folosiți ''zip'' și ''min''. 4.3. Întoarce paritatea și cea mai bună potrivire pentru o cifră din grupul din stânga (cifrele 2-7). <code scala> def bestLeft(digitCode: SRL): (Parity, Digit) = ??? </code> ''Hint:'' Folosiți-vă de ''leftOddSRL'' și ''leftEvenSRL''. 4.4. Întoarce cea mai bună potrivire pentru o cifră din grupul din dreapta (ultimele 6 cifre). Pentru codificările R vom folosi tipul NoParity. <code scala> def bestRight(digitCode: SRL): (Parity, Digit) = ??? </code> ''Hint:'' Folosiți-vă de ''rightSRL''. 4.5. Primiți ca argument rezultatul lui ''runLength'' pe o listă de biți. Trebuie să verificați că secvența dată are lungimea de 59 (vedeți în secțiunea [[#Algoritm|Algoritm]] de ce). Delimitați primul grup (stânga) și al doilea grup (dreapta) folosindu-vă de ''chunksOf''. Ignorați verificarea pentru secvențele de start, centru și sfârșit (a fost făcută la checkRow). Pentru fiecare grup de 4 bare găsiți cea mai potrivită cifră. Reuniți rezultatele din cele două liste intr-una singură. Rezultatul va avea 12 cifre. <code scala> def findLast12Digits(rle: List[(Int, Bit)]): List[(Parity, Digit)] = ??? </code> ''Hint:'' Folosiți funcțiile drop și take pe listă pentru a separa grupurile de câte 6 cifre din partea stângă și din partea dreaptă. Va trebui să ignorați primele 3 bare, 5 bare de la mijloc și ultimele 3 bare. 4.6. Găsește prima cifră din codul de bare pe baza parităților cifrelor din primul grup (cel din stânga). Pentru prima cifră vom considera paritatea NoParity. <code scala> def firstDigit(l: List[(Parity, Digit)]): Option[Digit] = ??? </code> ''Hint:'' Puteți folosi ''zipWithIndex'' și ''leftParityList''. 4.7. Calculează cifra de control (ultima cifră) pe baza primelor 12 cifre dintr-un cod de bare. <code scala> def checkDigit(l: List[Digit]): Digit = ??? </code> ''Hint:'' Găsiți formula [[#Calculare cifră de control|aici]]. 4.8. Pentru un cod de bare dat verificați dacă este valid. Funcția primește o listă cu perechi de (Paritate, Cifra) și verifică dacă sunt 13 cifre și dacă cifra de paritate și cifra de control sunt corecte. <code scala> def verifyCode(code: List[(Parity, Digit)]): Option[String] = ??? </code> ''Hint:'' Folosiți ''firstDigit'' și ''checkDigit''. 4.9. Definiți funcția de solve care primește rezultatul lui ''runLength'' și întoarce cele 13 cifre reprezentate de codul de bare, dacă potrivirea găsită este un cod valid. <code scala> def solve(rle: List[(Int, Bit)]): Option[String] = ??? </code> ''Hint:'' Folosiți funcțiile ''findLast12Digits'', ''firstDigit'' si ''verifyCode''. ===== Testare ===== În folderul src/test din proiect se află Test.scala, care poate fi rulat din IntelliJ apăsând pe butonul de Run din partea stângă a codului. Fiecare test are un buton propriu și poate fi rulat separat. Există teste pentru cele mai importante funcții, vă recomandăm să testați funcțiile de bază înainte de a începe implementarea task-urilor avansate. La rularea testelor veți primi și un punctaj, după specificațiile din enunț. Dacă folosiți terminalul: <code>sbt test</code> ===== Submisie arhiva ===== <note important> Veti incarca pe moodle o arhivă ce conține, **in rădăcina acesteia**, folderul ''src'' al proiectului vostru, fișierul ''build.sbt'' și un fișier text, intitulat ''ID.txt'' ce conține o singură linie, și anume id-ul vostru anonim (pe care il puteti găsi pe moodle la assignment-ul ''tokenID''). Arhiva trebuie să fie **obligatoriu .zip**. </note> Exemplu structura arhivă: <code> archive.zip |-src/ | |-main/ | | |-scala/ | | | | - ... <fisierele cu sursa scala> |-build.sbt |-ID.txt </code> ===== Cum rulez pe propriile exemple? ===== Găsiți fișierul ''MyBarcodes.scala'' care poate fi rulat din IDE. Funcția ''readBarcodes'' primește numele unui folder de input și a unui folder de output. În cel de input puteți pune propriile imagini cu coduri de bare în format **.ppm**. Decupați doar codul de bare din imagini și aveți grijă ca acestea să fie suficient de clare, altfel algoritmul nu va funcționa. Puteți converti imagini în formatul **.ppm** pe https://convertio.co/. În folderul de output puteți vedea aceleași imagini **.ppm**, dar în format alb-negru (adică **.pbm**).