Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pp:24:teme:racket-st [2024/03/08 16:38]
mihaela.balint
pp:24:teme:racket-st [2024/03/21 11:34] (current)
mihaela.balint [Changelog]
Line 2: Line 2:
  
   * Data publicării:​ 03.03.2024   * Data publicării:​ 03.03.2024
-  * Data ultimei modificări: ​03.03.2024 ([[pp:​24:​teme:​racket-st#​changelog]])+  * Data ultimei modificări: ​21.03.2024 ([[pp:​24:​teme:​racket-st#​changelog]])
   * Tema (o arhivă .zip cu toate fișierele .rkt folosite în etapa curentă) se va încărca pe [[https://​vmchecker.cs.pub.ro/​ui/#​PP|vmchecker]]   * Tema (o arhivă .zip cu toate fișierele .rkt folosite în etapa curentă) se va încărca pe [[https://​vmchecker.cs.pub.ro/​ui/#​PP|vmchecker]]
 ===== Descriere generală și organizare ===== ===== Descriere generală și organizare =====
Line 145: Line 145:
  
 În această etapă veți implementa cei mai importanți constructori pentru tipul ST: În această etapă veți implementa cei mai importanți constructori pentru tipul ST:
-  * funcția ''​text->​ast''​ care primește un text și calculează arborele de sufixe atomic asociat textului +  * funcția ''​%%text->​ast%%''​ care primește un text și calculează arborele de sufixe atomic asociat textului 
-  * funcția ''​text->​cst''​ care primește un text și calculează arborele de sufixe compact asociat textului+  * funcția ''​%%text->​cst%%''​ care primește un text și calculează arborele de sufixe compact asociat textului
   ​   ​
 Algoritmul de construcție,​ descris și în scheletul de cod, este următorul: Algoritmul de construcție,​ descris și în scheletul de cod, este următorul:
Line 196: Line 196:
 (suffixes->​st labeling-func suffixes alphabet) (suffixes->​st labeling-func suffixes alphabet)
 </​file>​ </​file>​
-  * suffixes->​st primește o funcție de etichetare (precum ''​ast-func''​ sau ''​cst-func''​),​ o listă de sufixe (toate sufixele unui text) și un alfabet (alfabetul folosit de text) și întoarce:​ +  * ''​%%suffixes->​st%%'' ​primește o funcție de etichetare (precum ''​ast-func''​ sau ''​cst-func''​),​ o listă de sufixe (toate sufixele unui text) și un alfabet (alfabetul folosit de text) și întoarce:​ 
-    * AST-ul asociat textului, dacă apelăm ''​suffixes->​st''​ cu funcția de etichetare ''​ast-func''​ +    * AST-ul asociat textului, dacă apelăm ''​%%suffixes->​st%%''​ cu funcția de etichetare ''​ast-func''​ 
-    * CST-ul asociat textului, dacă apelăm ''​suffixes->​st''​ cu funcția de etichetare ''​cst-func''​+    * CST-ul asociat textului, dacă apelăm ''​%%suffixes->​st%%''​ cu funcția de etichetare ''​cst-func''​
   * practic, funcția descrie pasul 3 al algoritmului enunțat la începutul etapei   * practic, funcția descrie pasul 3 al algoritmului enunțat la începutul etapei
   * ex: ''​%%(suffixes->​st cst-func (get-suffixes (string->​list "​banana$"​)) (string->​list "​$abn"​))%%''​   * ex: ''​%%(suffixes->​st cst-func (get-suffixes (string->​list "​banana$"​)) (string->​list "​$abn"​))%%''​
     * primul argument este ''​cst-func'',​ al doilea argument sunt toate sufixele textului ''"​banana$"'',​ iar al treilea argument este un alfabet care include caracterele ''"​a"'',​ ''"​b"'',​ ''"​n"''​ și ''"​$"''​ folosite de textul ''"​banana$"''​     * primul argument este ''​cst-func'',​ al doilea argument sunt toate sufixele textului ''"​banana$"'',​ iar al treilea argument este un alfabet care include caracterele ''"​a"'',​ ''"​b"'',​ ''"​n"''​ și ''"​$"''​ folosite de textul ''"​banana$"''​
 +    * observație:​ alfabetul primit va fi parcurs în ordine (când funcția va fi invocată din funcția ''​%%text->​st%%'',​ acest alfabet va fi întotdeauna sortat; altfel, dacă ''​%%suffixes->​st%%''​ primește un alfabet nesortat, se va construi pur și simplu un arbore de sufixe cu altă ordonare a ramurilor decât cea alfabetică)
     * pentru caracterul ''"​$"'',​ algoritmul va selecta sufixul ''"​$"'',​ care generează o ramură de etichetă ''"​$"''​ sub care se găsește o frunză în arbore, întrucât după eliminarea prefixului ''"​$"''​ reprezentat de etichetă rămânem doar cu un nou sufix vid     * pentru caracterul ''"​$"'',​ algoritmul va selecta sufixul ''"​$"'',​ care generează o ramură de etichetă ''"​$"''​ sub care se găsește o frunză în arbore, întrucât după eliminarea prefixului ''"​$"''​ reprezentat de etichetă rămânem doar cu un nou sufix vid
     * pentru caracterul ''"​a"'',​ algoritmul va selecta sufixele ''"​anana$"'',​ ''"​ana$"'',​ ''"​a$"''​     * pentru caracterul ''"​a"'',​ algoritmul va selecta sufixele ''"​anana$"'',​ ''"​ana$"'',​ ''"​a$"''​
Line 222: Line 223:
 (text->​cst text) (text->​cst text)
 </​file>​  ​ </​file>​  ​
-  * text->​ast primește un text și calculează arborele de sufixe atomic asociat textului +  * ''​%%text->​ast%%'' ​primește un text și calculează arborele de sufixe atomic asociat textului 
-  * text->​cst primește un text și calculează arborele de sufixe compact asociat textului +  * ''​%%text->​cst%%'' ​primește un text și calculează arborele de sufixe compact asociat textului 
-  * cele două funcții trebuie obținute prin aplicația parțială a funcției mai generale ''​text->​st'';​ aceasta din urmă trebuie să fie o funcție curry proiectată de către voi (de aceea nu i-am specificat signatura), care efectuează pașii 1 (determinare alfabet sortat) și 2 (determinare sufixe la care se adaugă marcajul ''"​$"''​) ai algoritmului,​ apoi predă controlul funcției suffixes->​st care efectuează pasul 3 +  * cele două funcții trebuie obținute prin aplicația parțială a funcției mai generale ''​%%text->st%%'';​ aceasta din urmă trebuie să fie o funcție curry proiectată de către voi (de aceea nu i-am specificat signatura), care efectuează pașii 1 (determinare alfabet sortat) și 2 (determinare sufixe la care se adaugă marcajul ''"​$"''​) ai algoritmului,​ apoi predă controlul funcției ​''​%%suffixes->​st%%'' ​care efectuează pasul 3 
-  * ex: ''​%%(text->​cst "​banana"​)%%''​ va genera același rezultat cu exemplul anterior pentru funcția ''​suffixes->​st''​+  * ex: ''​%%(text->​cst "​banana"​)%%''​ va genera același rezultat cu exemplul anterior pentru funcția ''​%%suffixes->​st%%''​
   ​   ​
 ==== Depunctări generate de nerespectarea cerințelor din enunț ==== ==== Depunctări generate de nerespectarea cerințelor din enunț ====
 Baremul depunctărilor posibile în etapa 2 este: Baremul depunctărilor posibile în etapa 2 este:
   * -10p: get-suffixes nu este recursivă pe stivă   * -10p: get-suffixes nu este recursivă pe stivă
-  * -10p*''​n''​: unde ''​n'' ​= numărul de funcții dintre ''​get-ch-words'',​ ''​ast-func'',​ ''​cst-func''​ rezolvate fără a folosi funcționale în locul recursivității explicite +  * -10p*n: unde n = numărul de funcții dintre ''​get-ch-words'',​ ''​ast-func'',​ ''​cst-func''​ rezolvate fără a folosi funcționale în locul recursivității explicite 
-  * -10p*''​n''​: unde ''​n'' ​= numărul de pași din funcția ''​suffixes->​st''​ rezolvați fără a folosi funcționale în locul recursivității explicite (reamintim că funcția însăși poate fi recursivă explicit, pentru a realiza pasul de construcție a subarborilor,​ însă pentru alte prelucrări (ca gruparea pe ramuri, determinarea etichetelor,​ etc.) trebuie folosite funcționale +  * -10p*n: unde n = numărul de pași din funcția ''​%%suffixes->​st%%''​ rezolvați fără a folosi funcționale în locul recursivității explicite (reamintim că funcția însăși poate fi recursivă explicit, pentru a realiza pasul de construcție a subarborilor,​ însă pentru alte prelucrări (ca gruparea pe ramuri, determinarea etichetelor,​ etc.) trebuie folosite funcționale 
-  * -10p: ''​text->​ast''​ și ''​text->​cst''​ nu sunt obținute cu "efort minim" din ''​text->​st''​ (-5p dacă ''​text->​st''​ nu e curry, -5p dacă derivarea nu se face scriind minimul posibil)+  * -10p: ''​%%text->​ast%%''​ și ''​%%text->​cst%%''​ nu sunt obținute cu "efort minim" din ''​%%text->st%%''​ (-5p dacă ''​%%text->st%%''​ nu e curry, -5p dacă derivarea nu se face scriind minimul posibil) 
 + 
 +===== Etapa 3 ===== 
 + 
 +În această etapă veți implementa câteva aplicații ale arborilor de sufixe:  
 +  * căutarea unui subșir într-un text 
 +  * identificarea celui mai lung subșir comun a două texte 
 +  * căutarea unui subșir de lungime dată care se repetă în text 
 + 
 +Scopul principal al etapei este lucrul cu **expresii de legare statică a variabilelor**:​ 
 +  * let sau let* - pentru evitarea calculelor duplicate 
 +  * named let sau letrec - pentru implementarea ad-hoc a proceselor recursive, fără a apela la funcții ajutătoare 
 +Etapa testează de asemenea faptul că implementările voastre respectă bunele practici în ceea ce privește **abstractizarea** - funcția ''​repeated-substring-of-given-length''​ cere să manipulați arborii de sufixe doar prin interfața definită în fișierul **suffix-tree.rkt**. În etapa 4 veți modifica implementarea arborilor de sufixe și efectul respectării (sau nerespectării) barierei de abstractizare se va reflecta în necesitatea de a modifica sau nu implementarea funcției ''​repeated-substring-of-given-length''​. 
 +Vă reamintim că programarea funcțională este o programare de tip **wishful thinking**: veți descrie rezolvarea în termeni conceptuali,​ și veți implementa ulterior conceptele (subproblemele) care nu există deja în program (sub forma funcțiilor ajutătoare care rezolvă aceste subprobleme). 
 + 
 +Funcțiile pe care va trebui să le implementați sunt: 
 + 
 +<​file>​ 
 +(substring? text pattern) 
 +</​file>​ 
 +  * substring? primește un text și un șablon și întoarce true dacă șablonul apare în text, respectiv false dacă nu apare 
 +  * funcția este corespondenta funcției st-has-pattern?,​ diferența fiind că st-has-pattern?​ lucrează pe arbori de sufixe, iar substring? lucrează pe texte 
 +  * ex: ''​%%(substring?​ (string->​list "​banana"​) (string->​list "​bananas"​))%%''​ => ''#​f'',​ pentru că șablonul ''"​bananas"''​ nu apare în textul ''"​banana"''​ 
 + 
 +<​file>​ 
 +(longest-common-substring text1 text2) 
 +</​file>​ 
 +  * longest-common-substring primește două texte ''​text1''​ și ''​text2''​ și calculează cel mai lung subșir comun al acestora, astfel: 
 +    * construiește arborele de sufixe ST1 pentru ''​text1''​ 
 +    * determină toate sufixele S ale textului ''​text2''​ (fără marcajul de final ''​$'',​ pentru ca acesta să nu fie numărat drept caracter comun celor 2 texte) 
 +    * pentru fiecare sufix din S (de la cel mai lung la cel mai scurt), caută cea mai lungă potrivire a acestuia cu textul ''​text1''​ (parcurgând căile relevante în ST1); cea mai lungă asemenea potrivire este rezultatul final 
 +    * dacă există mai multe cele mai lungi subșiruri comune, funcția îl întoarce pe cel care a fost găsit primul urmând pașii de mai sus 
 +  * ex: ''​%%(longest-common-substring (string->​list "​babcxabac"​) (string->​list "​babxabxaaxxaba"​))%%''​ => ''​%%'​(#​\x #\a #\b #​\a)%%''​  
 +    * pentru sufixul ''"​babxabxaaxxaba"'',​ cea mai lungă potrivire a fost ''"​bab"''​ 
 +    * pentru sufixul ''"​abxabxaaxxaba"'',​ cea mai lungă potrivire a fost ''"​ab"''​ 
 +    * pentru sufixul ''"​bxabxaaxxaba"'',​ cea mai lungă potrivire a fost ''"​b"''​ 
 +    * pentru sufixul ''"​xabxaaxxaba"'',​ cea mai lungă potrivire a fost ''"​xab"''​ 
 +    * pentru sufixul ''"​abxaaxxaba"'',​ cea mai lungă potrivire a fost ''"​ab"''​ 
 +    * ... 
 +    * pentru sufixul ''"​xaba"'',​ cea mai lungă potrivire a fost ''"​xaba"'',​ care este și cea mai lungă potrivire per total 
 +  * ex: ''​%%(longest-common-substring (string->​list "​banana"​) (string->​list "​bandana"​))%%''​ => ''​%%'​(#​\b #\a #​\n)%%''​ 
 +    * există 2 potriviri de lungime 3: ''"​ban"''​ și ''"​ana"'',​ dar este întoarsă prima, conform algoritmului 
 +    * dacă sufixele pentru textul ''"​bandana"''​ ar fi inclus marcajul ''​$'',​ am fi obținut rezultatul greșit ''​%%'​(#​\a #\n #\a #​\$)%%''​ 
 + 
 +<​file>​ 
 +(repeated-substring-of-given-length text len) 
 +</​file>​ 
 +  * repeated-substring-of-given-length primește un text și un număr natural ''​len''​ și caută un subșir de lungime ''​len''​ care se repetă în text 
 +  * funcția întoarce ori primul asemenea subșir găsit, ori false dacă nu există soluție 
 +  * algoritmul de căutare este următorul:​ 
 +    * pe fiecare ramură a arborelui de sufixe **compact** (atenție, acest algoritm nu funcționează pentru arborele de sufixe atomic!): 
 +      * caută un nod intern (un nod care are fii, ceea ce înseamnă că drumul până la acel nod este un prefix comun pentru două sau mai multe sufixe distincte, adică un subșir care se repetă în text) 
 +      * dacă nodul intern găsit reprezintă un prefix de lungime mai mare sau egală cu ''​len'',​ atunci întoarce primele ''​len''​ caractere ale acestui prefix  
 +      * altfel, continuă căutarea 
 +  * funcția presupune că ramurile arborelui sunt sortate alfabetic (conform metodei de construcție din etapa anterioară),​ așadar rezultatul va fi mereu prima soluție din punct de vedere alfabetic 
 +  * ex: ''​%%(repeated-substring-of-given-length (string->​list "​xabxabxaaxbbxabxabxaaxbb"​) 10)%%''​ => ''​%%'​((#​\a #\b #\x #\a #\b #\x #\a #\a #\x #\b) 
 +%%'',​ pentru că, deși mai există și soluțiile ''"​xabxabxaax"''​ și ''"​bxabxaaxbb"'',​ ''"​abxabxaaxb"''​ este prima din punct de vedere alfabetic 
 + 
 +==== Depunctări generate de nerespectarea cerințelor din enunț ==== 
 +Baremul depunctărilor posibile în etapa 3 este: 
 +  * -20p: longest-common-substring nu folosește named let pentru parcurgerea sufixelor 
 +  * -10p: repeated-substring-of-given-length nu manipulează arborele doar prin intermediul operatorilor tipului ST  
 +  * -20p: aplicări repetate ale acelorași funcții pe aceleași argumente (în loc de a evalua expresiile o singură dată și a prelua rezultatele în variabile cu ajutorul let-urilor) - nu se va aplica nicio depunctare pentru o scăpare izolată, dar se va aplica depunctarea în întregime pentru "​scăpări"​ multiple 
 + 
 +===== Etapa 4 ===== 
 + 
 +În etapa 3 ați putut observa că anumite aplicații (căutarea unui subșir în text, căutarea unui subșir de o anumită lungime care se repetă în text) nu necesită decât explorarea parțială a arborelui de sufixe asociat textului, prin urmare ar fi mai eficient ca acest arbore să fie construit "​leneș",​ pe măsură ce diverse porțiuni din el sunt necesare. 
 + 
 +În acest scop, în etapa 4, veți modifica implementarea tipului ST astfel încât fiecare arbore (și, la rândul lor, toți subarborii aferenți) să fie nu o listă de ramuri, ci un flux de ramuri. Dacă aceasta ar fi singura modificare, singurele funcții care ar trebui redefinite ar fi constructorii și operatorii tipului ST (presupunând că ați respectat **bariera de abstractizare** a acestui tip). 
 + 
 +Întrucât arborii sunt construiți pe baza tuturor sufixelor unui text, veți modifica de asemenea reprezentarea tuturor sufixelor unui text - ele vor fi reținute într-un flux, nu într-o listă cum s-a întâmplat până acum. Pentru că multe funcții din etapele anterioare primeau ca argumente sau întorceau liste de sufixe, va trebui să redefiniți,​ de asemenea, toate aceste funcții. Așa cum este explicat în scheletul de cod, această redefinire "în masă" putea fi evitată dacă am fi lucrat de la început cu conceptul de "​colecție de sufixe",​ în loc să presupunem că acestea vor fi grupate neapărat într-o listă. Una din cerințele etapei este să realizați acum acest re-design (implementând un nou tip de date Collection),​ și să observați cum el vă ajută să jonglați cu ușurință între reprezentările alternative pentru colecțiile de sufixe. 
 + 
 +Este important să distingeți între colecțiile care devin fluxuri și cele care își păstrează vechea reprezentare:​ 
 +  * arborii devin fluxuri de ramuri 
 +  * ramurile rămân perechi etichetă-subarbore 
 +  * listele de sufixe/​cuvinte devin fluxuri de sufixe/​cuvinte 
 +  * sufixele/​cuvintele/​etichetele/​textele rămân liste de caractere 
 + 
 +Dintr-un anumit punct de vedere este o etapă ușoară, întrucât nu necesită implementarea unor funcții noi, ci doar ajustarea celor vechi (plus definirea tipului Collection). Dificultatea etapei constă în următoarele aspecte: 
 +  * aveți mai multă independență și responsabilitate în ceea ce privește designul programului 
 +    * vă veți defini singuri constructorii și operatorii utili pentru tipul Collection (fără să se spună în schelet fiecare funcție de care aveți nevoie) 
 +    * nu vom descrie din nou fiecare funcție care trebuie "​ajustată",​ întrucât comportamentul este același cu cel anterior, diferă eventual doar modul de reprezentare a datelor; voi înșivă trebuie să acționați conform specificației de mai sus și să înțelegeți când parametrii/​rezultatul unei funcții au devenit, din liste, fluxuri 
 +    * sunteți responsabili pentru eficiența implementărilor,​ astfel încât, la rularea testelor, să vă încadrați în limita de timp impusă pe vmchecker; jumătate din teste folosesc un text lung, pentru care o implementare corectă cu fluxuri ar trebui să fie mult mai rapidă decât implementarea anterioară cu liste 
 +  * fără o înțelegere foarte bună a datelor cu care lucrați, nu veți ști când trebuie utilizate liste și funcții pe liste și când trebuie utilizate fluxuri și funcții pe fluxuri 
 + 
 +Scopul etapei este consolidarea cunoștințelor legate de: 
 +  * **fluxuri**  
 +  * **abstractizare** (pe de o parte veți observa efectul respectării sau nerespectării barierei de abstractizare în etapele anterioare, pe de altă parte veți face un re-design care să abstractizeze și mai mult soluția, care va deveni astfel mai flexibilă) 
 + 
 +În realitate, implementarea cu fluxuri ne ajută doar atunci când evităm astfel explorarea unei porțiuni semnificative din arbore. Pentru aplicații precum găsirea celui mai lung subșir comun a două texte, care necesită căutarea în întreg arborele, este mai eficientă reprezentarea cu liste, pentru că operațiile pe liste sunt mai rapide decât cele pe fluxuri. În săptămânile următoare, veți vedea că în limbajul Haskell nu avem dificultatea acestei alegeri, întrucât în acest limbaj toate listele sunt, de fapt, fluxuri. Sperăm că tema noastră v-a ajutat să înțelegeți mai bine anumite concepte din programare și v-a trezit interesul pentru ce va urma.  
 + 
 +==== Depunctări generate de nerespectarea cerințelor din enunț ==== 
 +Baremul depunctărilor posibile în etapa 4 este: 
 +  * -10p: anumite funcții din fișierul etapa4.rkt sunt scrise să ruleze pe fluxuri, nu pe colecții (adică folosesc operatori de tip stream-*, nu pe cei de tip collection-* implementați de voi, cu care s-ar putea jongla ușor între reprezentarea cu liste și cea cu fluxuri) 
 +  * -6p*n: unde n = numărul de funcții dintre cele testate la exercițiul 1 (din checker) care, în loc să lucreze pe fluxuri, fac conversii între cele două tipuri de date pentru a lucra pe liste 
 +  * -30p: ''​%%suffixes->​st%%'',​ în loc să lucreze pe fluxuri, face conversii între cele două tipuri de date pentru a lucra pe liste 
 +În afară de aceste depunctări,​ nota va fi, în principiu, cea obținută pe vmchecker. Întrucât nu putem anticipa cât de bine va discerne timeout-ul de pe vmchecker între soluțiile implementate conform specificației și celelalte, ne rezervăm dreptul să efectuăm ajustări manuale în ambele sensuri: 
 +  * vom depuncta (total) soluțiile implementate cu liste care reușesc să ia punctaj (probabil parțial) pe vmchecker 
 +  * vom oferi punctaj (parțial sau total) soluțiilor care **sunt implementate corect cu fluxuri**, dar care nu se încadrează în timp din cauza unor probleme de eficiență la alte niveluri 
 +    * vom acorda punctajul total când problemele de eficiență sunt minore, soluția fiind totuși destul de rapidă (nu necesită mai mult decât dublul timpului cerut pe vmchecker) 
 +    * vom acorda 50% din punctaj când problemele de eficiență sunt majore
  
 ===== Precizări ===== ===== Precizări =====
Line 247: Line 348:
   * {{:​pp:​24:​teme:​racket:​etapa1.zip|etapa 1}}   * {{:​pp:​24:​teme:​racket:​etapa1.zip|etapa 1}}
   * {{:​pp:​24:​teme:​racket:​etapa2.zip|etapa 2}}   * {{:​pp:​24:​teme:​racket:​etapa2.zip|etapa 2}}
 +  * {{:​pp:​24:​teme:​racket:​etapa3.zip|etapa 3}}
 +  * {{:​pp:​24:​teme:​racket:​etapa4.zip|etapa 4}}
  
 ===== Changelog ===== ===== Changelog =====
 +  * 21.03 (ora 11:30) - Am adăugat o mică depunctare (10p) în etapa 4, pentru cazul în care funcțiile redefinite sunt redefinite exclusiv pentru fluxuri (folosind operatori de tip stream-*), nu pentru colecții (folosind operatori de tip collection-*),​ nerealizând astfel abstractizarea cerută în enunț (care ne permite să lucrăm alternativ cu liste sau cu fluxuri, efectuând modificări doar în interfața tipului Collection, nu și în restul funcțiilor).
 +  * 18.03 (ora 11:45) - Am publicat etapa 4.
 +  * 14.03 (ora 21:15) - Am publicat etapa 3.
 +  * 14.03 (ora 15:05) - Am adăugat o observație în etapa 2, la descrierea funcției ''​%%suffixes->​st%%'':​ funcția nu își sortează alfabetul; ramurile arborelui generat vor fi ordonate conform ordinii caracterelor din alfabetul primit ca argument.
   * 08.03 (ora 16:30) - Am publicat etapa 2.   * 08.03 (ora 16:30) - Am publicat etapa 2.
   * 03.03 (ora 20:45) - Am publicat etapa 1.   * 03.03 (ora 20:45) - Am publicat etapa 1.
Line 255: Line 362:
   * [[https://​hollywood.zbh.uni-hamburg.de/​uploads/​pubs/​pdf/​GieKur1995a.pdf|A comparison of imperative and purely functional suffix tree constructions]]   * [[https://​hollywood.zbh.uni-hamburg.de/​uploads/​pubs/​pdf/​GieKur1995a.pdf|A comparison of imperative and purely functional suffix tree constructions]]
   * [[https://​en.wikipedia.org/​wiki/​Suffix_tree|Suffix tree - Wikipedia]]   * [[https://​en.wikipedia.org/​wiki/​Suffix_tree|Suffix tree - Wikipedia]]
- 
- 
  
pp/24/teme/racket-st.1709908687.txt.gz · Last modified: 2024/03/08 16:38 by mihaela.balint
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0