• Nenhum resultado encontrado

Recursivitatea în limbajele bazate pe reguli

10.1. Turnurile din Hanoi

Capitolul 10

Recursivitatea în

Programarea bazată pe reguli 136

- mut al n-lea disc de pe A pe B

- transfer, în acelaşi mod, n-1 discuri de pe C pe B folosind A drept intermediar

sau, într-o scriere pseudo-cod în care one-move(a, b) semnifică mutarea unui disc de pe a pe b (vezi şi Figura 32):

Figura 32: Turnurile din Hanoi

1. procedure hanoi(n, a, b, c) ; n – numarul de discuri ; a – suportul de plecare ; b – suportul de destinatie ; c – suportul ajutator 2. begin

3. if n=1 then one-move(a, b) else 1

3 2

A B C

A B C

A B C

A B C

Recursivitatea în limbajele bazate pe reguli 137

4. begin

5. hanoi(n-1, a, c, b) 6. one-move(a, b) 7. hanoi(n-1, c, b, a) 8. end

9. end

Pentru transpunerea acestui algoritm în reguli, va trebui, aşa cum am mai arătat, să introducem o structură de date care să aibă comportamentul unei stive. La început stiva va conŃine apelul iniŃial al procedurii recursive hanoi, apoi, la fiecare pas, apelul din vârful stivei va fi interpretat astfel: dacă e vorba de un apel recursiv – atunci el va fi expandat într-o secvenŃă de trei apeluri: un prim apel recursiv, o mutare elementară şi un al doilea apel recursiv, iar dacă este vorba de o mutare elementară – ea va fi executată.

Pentru simplificarea operaŃiilor ce urmează a se executa asupra stivei, preferăm să reprezentăm toate apelurile ca obiecte, argumentele unui apel fiind valori ale unor atribute ale acestor obiecte. Iată o posibilă reprezentare:

(deftemplate hanoi (slot index) (slot nr-disc) (slot orig) (slot dest) (slot rez) )

(deftemplate muta (slot index) (slot orig) (slot dest) )

Obiectul hanoi descrie un apel recursiv; obiectul muta descrie un apel- mutare elementară; index păstrează un index unic de identificare a apelului; nr- disc precizează numărul de discuri; orig, dest în ambele tipuri de obiecte reprezintă indecşii suporturilor de origine şi respectiv destinaŃie; rez dă indexul suportului de manevră.

În regulile de mai jos respectăm ordinea operaŃiilor asupra stivei, astfel încât numai obiectul aflat în vârful stivei să fie prelucrat la oricare intervenŃie asupra stivei. În funcŃie de tipul obiectului din vârf, trei operaŃii pot avea loc:

- dacă obiectul este un apel recursiv cu un singur disc (nr-disc=1), el este transformat într-un apel de mutare:

Programarea bazată pe reguli 138

(defrule transforma-recursie1-in-mutare ?stf <- (stiva ?item $?end)

?hf <- (hanoi (index ?item) (nr-disc 1) (orig ?fr) (dest ?to)

(rez ?re)) =>

(retract ?stf ?hf) (bind ?idx (gensym))

(assert (muta (index ?idx) (orig ?fr) (dest ?to)) (stiva ?idx $?end))

)

- dacă obiectul este un apel recursiv cu mai mult decât un singur disc (nr-disc>1), el este expandat într-o secvenŃă conŃinând un apel recursiv, un apel de mutare şi un alt apel recursiv (vezi Figura 32):

(defrule expandeaza-hanoi ?stf <- (stiva ?item $?end) ?hf <- (hanoi (index ?item)

(nr-disc ?no&:(> ?no 1))

(orig ?fr) (dest ?to) (rez ?re)) =>

(retract ?stf ?hf) (bind ?idx1 (gensym)) (bind ?idx2 (gensym)) (bind ?idx3 (gensym)) (assert

(hanoi (index ?idx1) (nr-disc =(- ?no 1)) (orig

?fr)

(dest ?re) (rez ?to))

(muta (index ?idx2) (orig ?fr) (dest ?to)) (hanoi (index ?idx3) (nr-disc =(- ?no 1)) (orig

?re)

(dest ?to) (rez ?fr)) (stiva ?idx1 ?idx2 ?idx3 $?end)) )

- şi, în sfârşit, dacă obiectul este un apel de mutare, o acŃiune corespunzătoare este efectuată, în cazul de faŃă, tipărirea unui mesaj:

(defrule tipareste-mutare ?stf <- (stiva ?item $?end)

?hf <- (muta (index ?item) (orig ?fr) (dest ?to)) =>

(retract ?stf ?hf)

(printout t “muta disc de pe “ ?fr “ pe “ ?to crlf)

Recursivitatea în limbajele bazate pe reguli 139

(assert (stiva $?end)) )

Desigur o regulă iniŃială trebuie să amorseze procesul prin introducerea primului apel recursiv în stivă. Regula următoare realizează acest lucru după interogarea utilizatorului asupra numărului de discuri:

(defrule hanoi-initial (not (stiva $?)) =>

(printout t “Turnurile din Hanoi. Numarul de discuri?

“)

(bind ?n (read)) (bind ?idx (gensym))

(assert (hanoi (index ?idx) (nr-disc ?n) (orig A) (dest B) (rez C)) (stiva ?idx))

)

Să observăm că regulile transforma-recursie1-in-mutare şi tipareste-mutare pot fi concentrate într-una singură, caz în care nu am mai avea nevoie de obiecte de tip (muta ...).

O altă observaŃie, mult mai importantă, se referă la ordinea în care sunt consumate apelurile recursive. Ea este nerelevantă, pentru că ceea ce interesează este doar secvenŃa de mutări elementare cu care am rămâne în stivă după consumarea tuturor apelurilor recursive. Cu alte cuvinte, am putea înlocui stiva cu o listă de apeluri, din care să consumăm într-o primă etapă, în maniera arătată, toate apelurile recursive, pentru ca, într-o a doua etapă, să parcurgem în ordine lista pentru execuŃia mutărilor elementare.

Pentru a transforma ordinea de intervenŃie asupra obiectelor-apeluri din structura de memorare a apelurilor, dintr-una ultimul-venit-primul-servit6 (ceea ce face din structura de memorare o stivă) într-una aleatorie, doar următoarele modificări (marcate cu aldine mai jos) ar trebui operate asupra programului (toate regulile primesc în nume o terminaŃie – v1):

(defrule transforma-recursie1-in-mutare-v1 ?stf <- (stiva $?beg ?item $?end)

?hf <- (hanoi (index ?item) (nr-disc 1) (orig ?fr) (dest ?to) (rez ?re))

=>

(retract ?stf ?hf) (bind ?idx (gensym))

6 Engl. last-in-first-out.

Programarea bazată pe reguli 140

(assert (muta (index ?idx) (orig ?fr) (dest ?to)) (stiva $?beg ?idx $?end))

)

(defrule expandeaza-hanoi-v1

; un obiect oarecare (cu n>1) din lista de memorare este

; selectat

?stf <- (stiva $?beg ?item $?end)

?hf <- (hanoi (index ?item) (nr-disc ?no&:(> ?no 1)) (orig ?fr) (dest ?to) (rez ?re))

=>

(retract ?stf ?hf) (bind ?idx1 (gensym)) (bind ?idx2 (gensym)) (bind ?idx3 (gensym)) (assert

(hanoi (index ?idx1) (nr-disc =(- ?no 1)) (orig ?fr) (dest ?re) (rez ?to)) (muta (index ?idx2) (orig ?fr) (dest ?to)) (hanoi (index ?idx3) (nr-disc =(- ?no 1)) (orig ?re) (dest ?to) (rez ?fr)) (stiva $?beg ?idx1 ?idx2 ?idx3 $?end)) )

Dacă intervenŃiile asupra structurii de memorare în vederea transformării apelurilor recursive în mutări elementare se pot face acum în orice ordine, regula de tipărire este singura care impune respectarea unei ordini în descărcarea acestei structuri, de la ultimul element introdus spre primul, ceea ce ne aduce din nou cu gândul la stivă. De asemenea, întrucât descărcarea trebuie să fie ultima fază a algoritmului, vom da regulii care o realizează o prioritate mai mică:

(defrule tipareste-mutare-v1 (declare (salience -10)) ?stf <- (stiva ?item $?end)

?hf <- (muta (index ?item) (orig ?fr) (dest ?to)) =>

(retract ?stf ?hf)

(printout t “muta disc de pe “ ?fr “ pe “ ?to crlf) (assert (stiva $?end))

)

Recursivitatea în limbajele bazate pe reguli 141