Table of Contents

Racket: Arbori de sufixe

Descriere generală și organizare

Tema constă în definirea și utilizarea arborilor de sufixe asociați unui text și este împărțită în 4 etape:

Așa cum se poate observa, ziua deadline-ului variază în funcție de semigrupa în care sunteți repartizați. Restanțierii care refac tema și nu refac laboratorul beneficiază de ultimul deadline (deci vor avea deadline-uri în zilele de 20.03, 27.03, 03.04, 10.04).

Rezolvările tuturor etapelor pot fi trimise până în ziua laboratorului 6, dar orice exercițiu trimis după deadline (și până în ziua laboratorului 6) se punctează cu jumătate din punctaj. Orice exercițiu trimis după ziua laboratorului 6 nu se mai punctează deloc. Nota finală pe etapă se calculează conform formulei: n = (n1 + n2) / 2 (n1 = nota obținută înainte de deadline; n2 = nota obținută după deadline). Când toate submisiile sunt înainte de deadline, nota pe ultima submisie este și nota finală (întrucât n1 = n2).

În fiecare etapă, veți folosi ce ați învățat în săptămâna anterioară pentru a dezvolta aplicația.

Pentru fiecare etapă veți primi un schelet de cod (dar rezolvarea se bazează în mare măsură pe rezolvările anterioare). Enunțul din această pagină este menit să descrie arborii de sufixe (pe care îi vom numi ST, conform prescurtării în limba engleză) și să vină cu exemple de rulare a funcțiilor din schelet. Dacă preferați, puteți rezolva tema utilizând doar indicațiile din schelet.

Etapa 1

În prima etapă vă veți familiariza cu structura și reprezentarea arborilor de sufixe (ST) în Racket, veți implementa o mini-bibliotecă pentru tipul ST (în fișierul suffix-tree.rkt), apoi veți implementa câteva funcții utile construcției și manipulării arborilor de sufixe (în fișierul etapa1.rkt).

Un arbore de sufixe este un arbore care stochează toate sufixele unui text T, astfel:

Convenim să terminăm fiecare sufix prin caracterul special $, asigurându-ne astfel că fiecărui sufix îi va corespunde o frunză în arbore. Pentru textul “BANANA”, arborele de sufixe arată astfel:

Observăm că, fără convenția de a utiliza terminația $, sufixului “ANA” i-ar fi corespuns un nod intern în arborele de sufixe, nu o frunză.

Reprezentare în Racket

Rezultă că reprezentarea completă în Racket a arborelui din figură este:

'( ((#\$))                          ; prima ramură, de etichetă "$", corespunzătoare sufixului vid
   ((#\A)                           ; a doua ramură, de etichetă "A"
        ((#\$))                        ; ramura 2.1, terminală, corespunzătoare sufixului "A"
        ((#\N #\A)                     ; ramura 2.2, de etichetă "NA"
             ((#\$))                     ; ramura corespunzătoare sufixului "ANA"
             ((#\N #\A #\$))))           ; ramura corespunzătoare sufixului "ANANA"
   ((#\B #\A #\N #\A #\N #\A #\$))  ; a treia ramură, de etichetă "BANANA$"
   ((#\N #\A)                       ; a patra ramură, de etichetă "NA"
        ((#\$))                        ; ramura corespunzătoare sufixului "NA"
        ((#\N #\A #\$)))  )            ; ramura corespunzătoare sufixului "NANA"

Acești arbori vor fi definiți în checker, însă este necesar să le înțelegeți structura pentru a putea implementa funcțiile din cerință.

În etapa 1, veți exersa lucrul cu:

În următoarele exemple, considerăm că reprezentarea arborelui de sufixe pentru textul “BANANA” este reținută în variabila st-banana.

Funcțiile principale pe care le veți implementa sunt:

(first-branch st)
(other-branches st)
(get-branch-label branch)
(get-branch-subtree branch)
(get-ch-branch st ch)
(longest-common-prefix w1 w2)
(longest-common-prefix-of-list words)
(match-pattern-with-label st pattern)
(st-has-pattern? st pattern)

Depunctări generate de nerespectarea cerințelor din enunț

În enunțul anumitor exerciții apar restricții. Nerespectarea acestora duce la depunctări conform următorului barem:

Etapa 2

În această etapă veți implementa cei mai importanți constructori pentru tipul ST:

Algoritmul de construcție, descris și în scheletul de cod, este următorul:

  1. se determină și se sortează alfabetul folosit de text
  2. se determină toate sufixele textului
  3. pentru fiecare simbol din alfabetul sortat:
    • determină lista S a tuturor sufixelor care încep cu acest simbol
    • determină eticheta ramurii care va începe cu acest simbol:
      • pentru un AST, eticheta este chiar simbolul (ca listă de caractere, pentru uniformitate)
      • pentru un CST, eticheta este cel mai lung prefix comun al sufixelor din S
    • calculează fiecare ramură din arbore, ca pereche între:
      • eticheta determinată anterior
      • subarborele construit recursiv (pe baza sufixelor rămase în S după ce am eliminat prefixul comun reprezentat de etichetă)

Exercițiile valorifică faptul că, în programarea funcțională, funcțiile sunt valori de ordinul întâi. Scopul etapei este consolidarea cunoștințelor legate de:

În toate funcțiile de mai jos, când folosim noțiuni precum text, cuvânt sau sufix, ne referim la entități reprezentate ca liste de caractere. Funcțiile pe care va trebui să le implementați sunt:

(get-suffixes text)
(get-ch-words words ch)
(ast-func suffixes)
(cst-func suffixes)
(suffixes->st labeling-func suffixes alphabet)
(text->ast text)
(text->cst text)

Depunctări generate de nerespectarea cerințelor din enunț

Baremul depunctărilor posibile în etapa 2 este:

Etapa 3

În această etapă veți implementa câteva aplicații ale arborilor de sufixe:

Scopul principal al etapei este lucrul cu expresii de legare statică a variabilelor:

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:

(substring? text pattern)
(longest-common-substring text1 text2)
(repeated-substring-of-given-length text len)

Depunctări generate de nerespectarea cerințelor din enunț

Baremul depunctărilor posibile în etapa 3 este:

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:

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:

Scopul etapei este consolidarea cunoștințelor legate de:

Î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:

Î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:

Precizări

Resurse

Changelog

Referinţe