Prolog Recursion Lesson

Post on 21-Jul-2015

92 views 0 download

Transcript of Prolog Recursion Lesson

Recursion

Prolog Dersleri - 3.Hafta

Tekrarlama ve

rekürsiyon

Prosedür ve veri yapılarında tekrarlama iĢlemleri Visual Prolog‟da kolay bir şekilde yapılır. Bu bölümde önce tekrarlı işlemler (döngüler ve rekursif prosedürler), daha sonra ise rekursiv veri yapıları incelenecektir.

Tekrarlı İşlemler

Pascal, BASIC veya C gibi konvansiyonel programlama dilleriyle çalışanlar, Prologla çalışmaya başladıklarında FOR, WHILE, REPEAT gibi ifadeleri göremeyince şaşırabilirler. Çünkü Prologda iterasyonu anlatan direkt bir yol yoktur. Prolog sadece iki türlü tekrarlama-geriye dönüş imkanı tanır. Bu işlemlerde bir sorguya birden fazla çözüm bulmak ve bir prosedürün kendisini çağırdığı rekürsiyon işlemine imkan tanır.

Geriye İz Sürme

•Bir prosedür, istenilen bir hedef için uygun bir çözüm yerine alternatif başka çözümler aramak için geriye döner. Bunun için geriye henüz denenmemiş bir alternatifi kalan en son alt hedefe gidileceğini, bu noktadan tekrar aşağıya doğru inileceği bilinmektedir. Geriye dönüşü iptal edip tekrarlı işlemler yaptırmak mümkündür.

Örnek•PREDICATES

•nondeterm ulke_adi(symbol)

•ulke_adlarini_yaz

•CLAUSES

•ulke_adi("Türkiye").

•ulke_adi("Kazakistan").

•ulke_adi("Azerbaycan").

•ulke_adi("Amerika").

•ulke_adlarini_yaz:-

•ulke_adi(Ulke), write(Ulke), nl, fail.

•ulke_adlarini_yaz.

•GOAL ulke_adi(Ulke).

Yukarıdaki ulke_adi yüklemi sadece ülke isimlerini sıralar. Dolayısıyla GOAL ulke_adi(Ulke) şeklindeki bir hedefin birden fazla sonucu vardır ve ulke_adlarini_yaz yuklemi bunların hepsini görüntüler.

•ulke_adlarini_yaz :- ulke_adi(Ulke), write(Ulke), nl, fail. satırıyla söylenmek istenen şey şudur: “Bütün ülke isimlerini yazmak için, önce ulke-adi(Ulke) cümlesine cevap bul, bunu yaz, yeni bir satıra geç ve işlemi yeniden başlat.”

•„fail‟ komutunun programa yüklediği görev şöyle özetlenebilir: “GOAL cümlesine uygun bir çözüm bulunduğunda, geriye dönüş yap ve başka alternatiflere bak”.

•„fail‟ yerine, sonucu daima yanlış olan ve bu yüzden geriye dönüşü zorlayan başka bir alt hedef kullanmak mümkündür. Örneğin, 10=5+6 satırı her zaman yanlış olacağı için, Prolog başka alternatifler bulmak için daima geriye dönüş yapar.

•Örneğimizde ilk önce Ulke=Türkiye olur ve sonuç ekrana yazılır. „fail‟ komutuna sıra geldiğinde program, bir alt hedefe geri döner. Fakat nl veya write(Ulke) satırları için kullanılabilecek herhangi bir veri olmadığı için, bilgisayar ulke_adi(Ulke) iliĢkisi için baĢka çözümler arar.

•Ulke_adi(Ulke) iliĢkisi çalıĢtırıldığında, önceden boĢ değiĢken olan Ulke değiĢkeni „Türkiye‟ değerini almıĢtı. Bu yüzden bu iliĢkiyi yeniden kullanmadan önce Ulke değiĢkeni yeniden serbest hale getirilir. Daha sonra Ulke değiĢkeninin alabileceği baĢka bir olgu aranır. Ġkinci oluguda bu sağlanır ve ulke_adi yüklemindeki Ulke değiĢkeni „Kazakistan‟ değerini alır. Bu iĢlem böylece devam eder ve sonuçta Ģu satırlar görüntülenir.

•Türkiye

•Kazakistan

•Azerbaycan

•Amerika

•4 Solutions

•Eğer ulke_adlarini_yaz yüklemi „fail‟ komutundan sonra yazılmamıĢ olsaydı, cevap yine aynı olurdu fakat „yes‟ yerine „no‟ satırı görüntülenirdi.

Önceki ve Sonraki Eylemler Bir hedef için gerekli olan bütün çözümleri sağlayan bir program, çözüm yapmadan ve yaptıktan sonra başka şeyler de yapabilir. Örneğin

1. Yaşanacak güzel yerler

2. Ulke_adi(Ulke) yükleminin bütün sonuçlarını yaz.

3. Başka yerler de olabilir...

şeklinde bir mesaj yazarak bitirebilir.

Ulke_adlarini_yaz cümlesin ulke_adi(Ulke) yükleminin bütün sonuçlarını içerir ve sonunda bir bitiş mesajı yazar.

Örnekte geçen ilk ulke_adlarini_yaz cümlesi yukarıdaki adımlardan ikincisi içindir ve bütün çözümleri yazar. ikinci cümlesi ise üçüncü adıma tekabül eder ve sadece hedef cümlesini başarılı bir şekilde bitirmek içindir. Çünkü ilk cümle daima yanlıştır.

•Programı başka şekilde yazmak gerekirse:

•PREDICATES

•nondeterm ulke_adi(symbol)

•ulke_adlarini_yaz

•CLAUSES

•ulke_adi("Türkiye").

•ulke_adi("Kazakistan").

•ulke_adi("Azerbaycan").

•ulke_adi("Amerika").

•ulke_adlarini_yaz:-

•write("YaĢanacak bazı yerlerin listesi.."), nl, fail.

•ulke_adlarini_yaz :-

•ulke_adi(Ulke), write(Ulke), nl, fail.

•ulke_adlarini_yaz:-

•write("BaĢka güzel yerler de vardır..."), nl.

•İlk cümledeki „fail‟ komutu çok önemlidir. Çünkü bu komut ilk cümle çalıştırıldıktan sonra programın ikinci cümleye geçişini sağlar. Buradaki write ve nl http://alikoker.name.tr

•76

•komutlarının başka bir iş yapmaması çok önemlidir. Son „fail‟ komutundan sonra programın ikinci cümleciğe geçişi sağlanmalıdır.

Döngülü Geriye Dönüşün

Uygulanması Geriye dönüş işlemi bir hedefin bütün çözümlerinin bulunması açısından son derece

önemlidir. Birden fazla çözüm sunamayan hedefler için yine de geriye dönüş işlemi

yapılabilir. Bu da tekrarlama işlemini yapar. Örneğin:

tekrar.

tekrar:-tekrar.

gibi iki cümlecik sonsuz sayıda çözüm olduğunu göstermektedir.

Örnek:

PREDICATES

nondeterm tekrar

nondeterm karakteri_ekrana_yaz

CLAUSES

tekrar.

tekrar:-tekrar.

karakteri_ekrana_yaz:-

tekrar, readchar(Harf), /*Klavyeden girilen harfi oku ve C'ye ata*/

write(Harf),

Harf='\r', !. /* Satır sonu tuşuna (Enter/Return) basılmadıysa devam et*/

•GOAL karakteri_ekrana_yaz, nl.Yukarıdaki örnekte tekrar işleminin nasıl yapılacağını görülebilir. Karakteri_ekrana_yaz:-... kuralı, „Enter/Return‟ basılmadığı müddetçe, klavyeden girilen kararterleri kabul edip ekranda gösteren bir prosedür tanımlamaktadır.

•Karakteri_ekrana_yaz kuralının çalışma mekanizması şöyle sıralanabilir:

•1. tekrar‟ı çalıştır. (Hiçbir şey yapmaz)

•2. bir karakter oku (Harf)

•3. Harf karakterini yaz

•4. Harf‟in satır sonu karakteri olup olmadığını kontrol et.

•5. Eğer satır sonu elemanı ise, işlemi bitir, değilse, geriye iz sürme işlemini yap ve alternatif ara. Buradaki write ve readchar kurallarının hiçbiri alternatif sağlayamaz. Dolayısıyla geriye dönüş hemen tekrar kuralına gider, bunun ise alternatif sunması tabiidir.

•6. İşlem devam eder. Bir karakter oku, onu ekrana yaz, satır sonu elemanı olup olmadığını kontrol et.

Rekursif Prosedürler (A³)

● Kendisini çağırabilen prosedüre rekursif prosedür

diyoruz.

● Tekrarlama işlemin yapmanın diğer bir yolu da

rekursiyondur.

● Rekursif prosedürler yaptıkları işlerin sayısını, toplamını

veya işlmelerin ara sonuçlarını saklayabilir ve bunları bir

döngüden diğerine rahatlıkla aktarabilirler.

Örnek: N sayısının faktöryelini (N!) hesaplamak

Standart Sistem

● Başlangıç değeri 1 olan bir

return değişkeni oluştur.

● 1 den N e kadar bir döngü

oluştur.

● return değerini döngünün

indisi ile çarparak return

değişkenine ata.

● Sonuç return değişkenidir.

Rekursif Sistem

● Kural 1: N sayısı 0 ise sonuç

1 dir.

● Kural 2: N sayısı 0 den

büyük ise sonuç N-1

faktöryeldir ((N-1)!).

Örnek Kod

factorial(0,1).

factorial(N,F) :-

N>0,

N1 is N-1,

factorial(N1,F1),

F is N * F1.

Ama ??

Faktöriyel kuralını N=6 olacak şekilde

çağırılırsa, faktöriyel kendini N=5 için

çağırılacaktır. Bu durumda N deki 6 değeri

nereye gitti ?

Rekursif Prosedürlerin Avantajları

● Mantıksal olarak iterasyondan çok daha basittir.

● Listeleri işlemede çok yaygın olarak kullanılır.

● Rekursiyon işlemi özellikle probleme içerisinde dallanmaların mevcut

olduğu, yani bir problemin çözümünün bir alt probleme bağlı olduğu

durumlarda çok faydalıdır.

Sondan Rekursion Optimizasyonu

Rekursiyon işleminin en önemli dezavantajı, belleği fazlaca kullanmasıdır.

Bir prosedür başka bir alt prosedürü çağırdığında, çağrıyı yapan prosedürün

çağrıyı yaptığı anki çalışma durumu mutlaka kaydedilmelidir. Böylece çağrılan

işlemini bitirdikten sonra çağıran kaldığı yerden devam edebilir. Ama dallanma

çok fazla olursa hafızaya kaydettikleri de fazla olacağından bu bir hafıza

şişmesine bile neden olabilir.

Yani biz 6! i hesaplamak isterken otamatik olarak 5! , 4! , 3! , 2! , 1! , 0! i de

hesaplamış oluyoruz ve bu hesaplamalar için ekstradan hafızaya bunları atmış

oluyoruz.

Peki bunun için ne yapmalı?

Bir prosedürün, başka bir prosedürü kendisinin en son adımı olarak çağırdığını

düşünelim. Çağrılan prosedür görevini yaptıktan sonra, çağrıyı yapan prosedürün

yapması gereken başka bir şey kalmaz. Çağrıyı yapan prosedürün kendisinin çalışma

anını kaydetmesi gerekmez, çünkü o andaki bilgi artık gereksizdir. Çağrılan prosedür

biter bitmez, program akışı normal biçimde devam eder.

Bu durum daha açık olarak aşağıdaki şekilde ifade edilebilir. A prosedürünün B

prosedürünü, B prosedürünün ise C prosedürünü son adım olarak çağırdığını düşünelim.

B prosedürü C’yi çağırdığında, B’nin başka bir şey yapması gerekmez. Yani C‟nin o anki

çalışma durumunu B olarak kaydetmek yerine, B’nin kaydedilen eski durumun C’ya

aktarmak, depolanan bilgi içinde uygun değişiklik yapmak mümkündür. C bittiği zaman,

doğrudan A prosedürü tarafından çağrılmış gibi olacaktır.

Sondan Rekursiyonun Kullanımı

Prologda bir prosedürün başka bir prosedürü ‘kendisinin

en son adımı olarak çağırması’ bu prosedürü sondan

rekursiyon yapacaktır. Bu da bu prosedürün geriye dönüş

yapması olasılığını kaldırır. (Yani heryede kullanamayız.)

######yani

sayac(Sayi):-

write(Sayi), nl,

yeni_sayi=Sayi+1,

sayac(Yeni_sayi).

Sondan Rekursiyonu Engelleme

Engellemek için :

● Başka prosedürü son adımda çağırmamak.

● Başka bir alternatif bırakmak.

(Bu ne midir? *)

Rekursiyonda Cut Kullanımı

Rekursif Prosedürleri çalıştırırken bazı noktalarda durdurmamız gerekir. bunun

için içinde bir koşul yazarak bu prosedürü durdurabiliriz.sayac(Sayi):-

Sayi>=0,!, /*Burada sayı 0 dan küçük olma durumunda boş bir sonuç döndürür*/

Sayi<1000,!, /*Burada ise sayı 1000 i geçtiği takdirde programı sonlandırması için*/

write(Sayi),

write(","),

Ysayi is Sayi+1,

sayac(Ysayi).

sayac(_):-

write("Sayi negatiftir.").

swipl_hakkinda.pl #oguzhancoo

%Değişken tanımları

?- assert(like(x,y)). %kabukta

like(x,y). %dosyada

%kabukta dosya çağırırken

?- [‘dosya.pl’].

%Konsole’da çalıştırırken

swipl -f dosya.pl

Args On The Loop%factorial

fact(Num,NumF):-

m_fact(Num,NumF,1,1).

m_fact(Num,NumF,I,P):-

I<=Num,!,

NewP=P*I, %kendisini yeni değerlerle çağırır

NewI=I+1, % //

m_fact(Num,NumF,NewI,NewP).

m_fact(Num,NumF,I,P):-

I>Num,

NumF=P.

%P=P+1 prologda değişken ataması yapmaz.

%YP = P+1 şeklinde yeni bir değişken sorunu çözecektir.

fact.pl%factorial

factorial(N,Fact) :-

fact_iter(N, 1, Fact).

fact_iter(0, SoFar, SoFar) :- !.

fact_iter(N, SoFar, Ans) :-

N1 is N - 1,

SoFar1 is N * SoFar,

fact_iter(N1, SoFar1, Ans).

%source http://www.cs.toronto.edu/~sheila/384/w11/simple-prolog-examples.html

hanoi.pl%hanoi towers

move(1,X,Y,_) :-

write('Disk '),

write(X),

write(' ten '),

write(Y),

write(' ye taşındı.'),

nl.

move(N,X,Y,Z) :-

N>1,

M is N-1,

move(M,X,Z,Y),

move(1,X,Y,_),

move(M,Z,Y,X).

Recursive Data Structures

recursive_exmp.pl

%A(B(D,E),C(F,G))

A

B C

D E F G

%Depth First Searching

tree_exmp.pl%DOMAINS

agac_yapisi=agac(string, agac_yapisi, agac_yapisi); bos_dal

%PREDICATES

agaci_tara(agac_yapisi)

%CLAUSES

agaci_tara(bos_dal).

agaci_tara(agac(Isim, Sol, Sag)):-

write(Isim, '\n'),

agaci_tara(Sol), agaci_tara(Sag).

%GOAL

agaci_tara(agac("Emine", agac("Ali", agac("Hasan", bos_dal, bos_dal),agac("Fatma", bos_dal, bos_dal)), agac("Ayse",

agac("Fuat", bos_dal, bos_dal),agac("Leyla", bos_dal, bos_dal)))).

swipl -s tree.exmp.pl ?-agaci_tara(agac("Emine", agac("Ali", agac("Hasan", bos_dal, bos_dal),agac("Fatma", bos_dal, bos_dal)),

agac("Ayse", agac("Fuat", bos_dal, bos_dal),agac("Leyla", bos_dal, bos_dal)))).

Emine

Ali

Hasan

Fatma

Ayse

Fuat

Leyla

yes

?-

ağaç oluşturmak

#s

Ağaç biçiminde bir yapı oluşturmanın bir yolu

operatörlerden ve argümanlardan oluşan iç içe

geçmeli bir yapı yazmaktır.

agac_olustur(Sayi, agac(Sayi, bos_dal, bos_dal))

# Eğer Sayi bir sayı ise, agac(Sayi, bos_dal,

bos_dal) tek hücreli bir ağaç olup veri olarak bu

sayıyı içerir.

sola_yerlestir(Sayi, agac(A, _,B), agac(A,Sayi,B))

#İlk ağacı, ikinci ağacın alt dalı olarak alır ve üçüncü ağacı

da sonuç olarak verir.

domains

agac_yapisi=agac(string,agac_yapisi,agac_yapisi)

tek daldan oluşan ağaç

agac_olustur(String, deger)

dal birleştirme

sola_yerlestir(deg1,degx,degy)

saga_yerlestir(deg1,degx,degy)