3. 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.
4. 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.
5. 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.
6. Ö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.
7. •Ö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.
8. Ö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.
9. •İ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.
10. 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.
•
11. 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.
12. Ö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)!).
14. 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 ?
15. 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.
16. 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.
17. 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.
18. 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).
20. 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.").
30. 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.
31. 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.
32. 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.
Bilgisayar kendi içinde her çağırıldığında bu prosedürü tekrardan kopyalar yani işlemler bitene kadar N değernin faktöryelini hesaplayacak bir sürü prosedür açılmış olur.
*Bu şu oluyor. Prosedürü yazdıktan sonra başka bir durum belirtmek yani.
factorial(N,F) :-
N>0,
N1 is N-1,
factorial(N1,F1),
F is N * F1.factorial(0,1).