Prolog – 2

Burada Herhangi_Biri, nefret_eder(ahmet, Herhangi_biri) alt hedefinin doğru olmadığı ispatlanmadan önce Herhangi_biri argümanı sever(esra, Herhangi_biri) alt hedefindeki Herhangi_biri argümanına atanmış olur. Dolayısıyla yukarıdaki kod tam olarak arzu edildiği gibi çalışır. Yukarıdaki kısa program

sever(ahmet, Herhangi_biri):-                 /* ‘Doğru çalışmaz’*/

not(nefret_eder(ahmet, Herhangi_biri),

sever(esra, Herhangi_biri).

şeklinde yazılacak olursa, ilk önce Not ile başlayan cümlecik çağrılmış olur. Not cümleciği içinde serbest değişken tanımlamak mümkün olmadığı için, Prolog hata mesajı vermiş olur. Çünkü not(nefret_eder(ahmet, Herhangi_Biri) cümleciğindeki Herhangi_biri argümanı serbest değişkendir ve hiçbir değeri yoktur. Programdaki Herhangi_biri yerine anonim değişken olan (_) kullanılsa bile, Prolog yine yanlış bir sonuç verecektir.

sever(ahmet, Herhangi_biri):-                 /*Doğru çalışmaz*/

not(nefret_eder(ahmet, _),

sever(esra, Herhangi_biri).

Çünkü yukarıdaki cümlelerden anlaşılan şudur: Eğer Ahmet’in nefret ettiği bir şey bilinmiyorsa ve eğer esra Herhangi_biri’ni seviyorsa, Ahmet Herhangi_biri’ni sever. Anlatılmak istenen şey ise şudur: Esra’nın sevdiği ve Ahmet’in de nefret etmediği Herhangi_biri varsa, Ahmet bu Herhangi_biri’ni sever.

Not yüklemini kullanırken çok dikkatli olmak gerekir. Yanlış kullanım, ya hata mesajı alınmasına ya da program içerisinde mantıksız bir yapıya neden olacaktır.

Örnek:

PREDICATES

Nondeterm alisveristen_hoslanir(symbol)

Nondeterm kredi_kartina_sahip(symbol, symbol)

kredisi_bitmis(symbol, symbol)

CLAUSES

alisveristen_hoslanir(Kim):-

kredi_kartina_sahip(Kim,Kredi_karti),not(kredisi_bitmis(Kim,Kredi_karti)),

write(Kim, Kredi_karti, “ile alışveriş yapabilir\n”).

kredi_kartina_sahip(yavuz, visa).

kredi_kartina_sahip(yavuz, diners).

kredi_kartina_sahip(ahmet, shell).

kredi_kartina_sahip(mehmet, masterkart).

kredi_kartina_sahip(asaf_bey, akbank).

kredisi_bitmis (yavuz, diners).

kredisi_bitmis (asaf_bey, masterkart).

kredisi_bitmis (yavuz, visa).

GOAL alisveristen_hoslanir(Kim).

 

Örnek:

DOMAINS

isim,cinsiyet,meslek,cisim,yardimci,madde = symbol

yas=integer

PREDICATES

nondeterm sahis(isim, yas, cinsiyet, meslek)

nondeterm iliskili(isim, isim)

ile_oldurdu(isim, cisim)

oldurdu(isim)

nondeterm katil(isim)

sebep(yardimci)

uzerinde_leke_var(isim, madde)

sahip(isim, cisim)

nondeterm birbirine_benzer(cisim, cisim)

nondeterm sahip_oldugu_cisim(isim, cisim)

nondeterm supheli(isim)

/* * * Katil hakkındaki gerçekler * * */

CLAUSES

sahis(huseyin,55,m,arastirma_gorevlisi).

sahis(yavuz,25,m,futbolcu).

sahis(yavuz,25,m,kasap).

sahis(ahmet,25,m,yankesici).

iliskili(fatma,ahmet).

iliskili(fatma,huseyin).

iliskili(deniz,ahmet).

ile_oldurdu(deniz,sopa).

oldurdu(deniz).

sebep(para).

sebep(kiskanclik).

sebep(durustluk).

uzerinde_leke_var(huseyin, kan).

uzerinde_leke_var(deniz, kan).

uzerinde_leke_var(yavuz, camur).

uzerinde_leke_var(ahmet, cikolata).

uzerinde_leke_var(fatma,cikolata).

sahip(huseyin,tahta_bacak).

sahip(ahmet,tabanca).

/* Temel Bilgiler */

birbirine_benzer(tahta_bacak, sopa).

birbirine_benzer(demir, sopa).

birbirine_benzer(makas, bicak).

birbirine_benzer(futbol_sopasi, sopa).

sahip_oldugu_cisim(X,futbol_sopasi):-sahis(X,_,_,futbolcu).

sahip_oldugu_cisim(X,makas):-sahis(X,_,_,kuafor).

sahip_oldugu_cisim(X,Cisim):-sahip(X,Cisim).

/* Susan’ın oldürüldüğü silaha sahip herkesi şüpheli kabul edilsin */

supheli(X):-

     ile_oldurdu(deniz,Silah) ,

     birbirine_benzer(Cisim,Silah) ,

     sahip_oldugu_cisim(X,Cisim).

/* Susan ile ilişkisi olan insanlar da şüpheliler listesine girmeli */

supheli(X):-

     sebep(kiskanclik),

     sahis(X,_,m,_),

     iliskili(deniz,X).

/* Susan’ın, ilişkilerinden haberdar olduğu bayanlar da şüpheli listemizde olmalı */

supheli(X):-

     sebep(kiskanclik),

     sahis(X,_,f,_),

     iliskili(X,Erkek),

     iliskili(deniz,Erkek).

/* Şüpheli, para için katil olan bir yankesici olabilir */

supheli(X):-

     sebep(para),

     sahis(X,_,_,yankesici).

katil(Katil):-

     sahis(Katil,_,_,_),

     oldurdu(Olduruldu),

     Olduruldu <> Katil, /* It is not a suicide */

     supheli(Katil),

     uzerinde_leke_var(Katil,Devam),

     uzerinde_leke_var(Olduruldu,Devam).

GOAL

     katil(Katil_Kim).

4.8. Prosedürel Açıdan Prolog

Prolog tanımsal yapıya sahip bir dildir. Program hazırlarken problem olgular(önceden bilinen gerçekler) ve kurallarla tanımlanır. Bunu takiben bilgisayardan çözüm bulması beklenir. Pascal, BASIC ve C gibi diller ise tamamen prosedürlere dayanırlar. Bilgisayarın belli bir probleme çözüm bulabilmesi için, programcının, yapılması gereken işlemleri alt programlar ve fonksiyonlar halinde adım adım tanımlaması gerekir.

4.8.1. Kurallar ve Olguların Prosedürlere Benzerliği

Prolog’daki bir kuralı diğer dillerdeki bir procedure olarak görmek mümkündür. Örneğin

sever(ahmet, Birsey):- sever(gul, Birsey).

şeklindeki bir kural ‘Ahmet’in bir şeyi sevdiğini ispatlamak için, Gül’ün de aynı şeyi sevdiğini ispat et’ anlamına gelir. Aynı şekilde

sever(Orhan, Baklava).

şeklindeki Prolog kuralı “Orhan’ın baklava sevdiğini ispat etmek için dur” anlamındaki bir procedure olarak düşünmek mümkündür. Burada sever(Kim, Ne) şeklinde bir sorgu gelirse, Kim ve Ne serbest değişkenleri sırasıyla Orhan ve Baklava değerlerini alırlar.

Case ifadesi, boolean testleri ve goto komutu diğer dillerde mevcut olan procedure örnekleridir. Benzer işlemleri, kuralları kullanarak yapabiliriz.

4.8.2. Bir Kuralın Case ifadesi Gibi Kullanılması

Prolog’daki Kural ile diğer dillerdeki Procedure arasındaki büyük farklardan biri, Prolog’un aynı procedure için birden fazla alternatif tanımlama imkanı vermesidir.

Pascal’daki CASE ifadesi kullanıyormuş gibi, her argüman değeri için farklı bir tanım yazarak çoklu tanım kullanabilirsiniz. Prolog kuralları peşpeşe kullanarak eşleşenleri bulur ve kuralın tanımladığı işlemi yapar.

Örnek:

PREDICATES

nondeterm basilan_tus(integer)

CLAUSES

basilan_tus(1):-

nl,

write(“1 Tuşuna Bastınız.”), nl.

basilan_tus(2):-

nl,

write(“2 Tuşuna Bastınız.”), nl.

basilan_tus(3):-

nl,

write(“3 Tuşuna Bastınız.”), nl.

basilan_tus(N):-

nl,

N<>1, N<>2, N<>3, write(“Hangi Tuşa Bastığınızı Bilmiyorum”), nl.

GOAL write(“1-3 arasında bir sayı yazınız: “), readint(Sayi), basilan_tus(Sayi).

Örnekte, 1, 2 veya 3 haricindeki rakamlar girildiğinde eşleşme olamaz ve çözüm bulunmaz.

4.8.3. Bir Kural İçinde Test Yapmak

Yukarıdaki örnekte geçen basilan_tus(N) cümleciğinde; verilecek herhangi bir değere otomatik olarak N’e atanacaktır. Burada verilen sayının sadece 1-3 aralığı dışında olması durumunda ‘Hangi Tuşa Bastığınızı Bilmiyorum’ mesajının görüntülenmesi gerekir. Bu N<>1, N<>2, N<>3 alt hedefleri ile gerçekleştirilmektedir. Prolog ilk önce yazılan değerin 1-3 arasında olup olmadığını doğrulamaya çalışır. Doğru olmadığını görünce geriye dönüş işlemi yapmaya çalışır. Fakat başka alternatif olmadığı için bu cümlenin geriye kalan kısmına geçiş yapamaz.

Basilan_tus ilişkisi atanacak olan seçeneklere dayanır. Eğer basilan_tus ilişkisi argümanı serbest olan bir değişken ile çağılırsa, GOAL bu cümlelerin hepsiyle eşleşir ve ilk üç kural alternatif çözümler olarak sunulur. Son cümle ise hataya neden olur. Çünkü bağımsız bir değişkeni bir sayı ile eşleştirmek mümkün değildir.

4.8.4. Cut Komutunun Goto Gibi Kullanılması

Yukarıdaki örnek vakit kaybına neden olur. Çünkü doğrulanan bir kural bulunsa bile, Prolog’un son kuralı da test edip başka alternatif araması gerekir. Prolog’a alternatif aramaktan vazgeçmesini söylemek için Cut komutu kullanılabilir. Bunun sonucu, bize zaman ve bellek tasarrufu sağlanır.

Yukarıdaki program, şimdi de Cut komutu ile yazılsın.

PREDICATES

nondeterm basilan_tus(integer)

CLAUSES

basilan_tus(1):- !,

nl,

write(“1 Tuşuna Bastınız.”), nl.

basilan_tus(2):- !,

nl,

write(“2 Tuşuna Bastınız.”), nl.

basilan_tus(3):- !,

nl,

write(“3 Tuşuna Bastınız.”), nl.

basilan_tus(_):- !, write(“Hangi Tuşa Bastığınızı Bilmiyorum”), nl.

GOAL write(“1-3 arasında bir sayı yazınız: “), readint(Sayi), basilan_tus(Sayi).

Cut komutunun işleme girebilmesi için Prolog’un içinde Cut bulunan bir kurala gitmesi ve Cut komutunun bulunduğu noktaya kadar gelmiş olması gerekir.

Cut komutu basilan_tus(X):- X>3, !, write(“Yazdığınız rakam çok yüksek”) gibi testlerden önce gelebilir. X>3 alt hedefi doğrulandığı anda Cut komutu önemli hale gelir. Burada kuralların sırası oldukça önemlidir. 13. örnekte kurallar; arzu edilen sıraya göre yazılabilir. Çünkü onlardan sadece biri girilen bir sayı ile eşleşir. Fakat Cut kullanılan yukarıdaki örnekte, bilgisayarın write(“Hangi Tuşa Bastığınızı Bilmiyorum”) kuralına diğer üç kuralı denemeden önce kesinlikle geçmediğinden emin olmak gerekir. Çünkü buradaki Cut komutları red_cut, yani olumsuz cut, görevi görürler ve programın mantığını değiştirirler.

Eğer yukarıdaki programda X<>1, X<>2 ve X<>3 şeklindeki karşılaştırma anahtarlarını tutup her cümleye sadece bir Cut komutu yerleştirilirse, buradaki Cut komutları Green Cut olarak görev yaparlar. Cut, diğer programlama dillerinde tıpkı GOTO komutu gibi görev görür. Cut komutunun kullanımı faydalıdır, fakat içinde Cut kullanılan programları anlamak bazen zor olabilir.

4.9. Hesaplanmış Değerleri Görüntüleme

Program akışında, ilk başta herhangi bir değeri olmayan argümanlar, daha sonra belirli değerle alırlar. Örneğin

sever(orhan, gulsah).

şeklindeki bir olgu

sever(orhan, Kim)

şeklindeki bir hedef cümlesindeki Kim argümanına ‘Gulsah’ değerini atamış olur. Yani

GOAL sever(orhan, Kim) hedef cümlesindeki Kim argümanı ilk önce serbest olmasına rağmen, sever(orhan, gulsah) olgusunu çağırdığı anda olgudaki Gulsah değeri Kim argümanına atanır ve Kim argümanı sınırlı hale gelir.

Örnek:

PREDICATES

nondeterm ekrana_yaz(integer, symbol)

CLAUSES

ekrana_yaz(0, sifir).

ekrana_yaz(Sayi, negatif):- Sayi<0.

ekrana_yaz(Sayi, pozitif):- Sayi>0.

GOAL ekrana_yaz(14, Sayinin_isareti).

‘ekrana_yaz’ ilişkisinin ilk argümanı daima sabit bir sayı ve bağlı bir değişken olmak zorundadır. İkinci argüman ise sınırlı veya sınırsız bir değişken olabilir. İlk değişkene bağlı olarak sıfır, negatif veya pozitif bir sayı olabilir.

Yukarıdaki GOAL cümleciğinin bulacağı cevap elbette yes olacaktır. Çünkü 14 sıfırdan büyük bir sayıdır ve pozitiftir. Burada sadece 3. cümlecik doğrudur.

GOAL ekrana_yaz(14, negatif).

Hedef cümlesiyle aynı prosedür takip edilir ve no cevabı alınır. Prolog’un sonuç alması incelenirse,

·        İlk önce ilk cümlecik incelenir. Belirle ilişkisindeki argümanlar, yani 14 ve negatif değerleri, 0 ve sifir değerleriyle eşleşmezler.

·        İkinci cümleciğe sıra geldiğinde, Sayi 14’e eşitlenir fakat sayi<0 testi doğrulanamaz.

·        Üçüncü argümana gelindiğinde bu kez ikinci argümanlar eşleşmez, yani pozitif kelimesi negatif ile eşleşemez.

Anlamlı bir cevap almak için örneğin GOAL belirle(14, Sayinin_Isareti) kullanılırsa,

Sayinin_Isareti=pozitif

1 Solution

cevabı alınır.

Yukarıdaki örneğin çalışması esnasında, işlemler aşağıdaki sıra ile yapılacaktır.

ekrana_yaz(14, Sayinin_Isareti) hedef cümlesi, ilk cümleciğin ekrana_yaz(0, sifir) kısmıyla eşleşmez. Bu yüzden ilk cümlecik kullanılamaz.

1.     ekrana_yaz(14, Sayinin_Isareti) hedef cümlesi ikinci cümleciğin baş kısmıyla eşleşir ve Sayi=14, Sayinin_Isareti=negatif olur. Fakat hemen sonraki Sayi<0 yanlış olduğundan Prolog bu cümlecikten geriye döner ve Sayi=14 değeri iptal edilir.

2.     ekrana_yaz(14, Sayinin_Isareti) hedef cümlesi üçüncü cümleciğin baş kısmıyla eşleşir ve Sayi=14, Sayinin_Isareti=pozitif olur. Sayi>0 eşitliği de sağlandığı için Prolog artık geriye iz sürme işlemini yapmaz ve sonucu görüntüler.


5. BASİT VE BİLEŞİK NESNELER

Şimdiye kadar Prolog’da kullanılan veri nesnelerinden number, symbol, string gibi birkaç tip incelenmiştir. Bu bölümde basit ve bileşik veri türlerinin tamamı incelenecektir. Standart tip olarak tanımlanabilen veriler, bazı bileşik veri yapılarını içermezler. Bu yüzden farklı veri yapılarına göz atarak, bunların domains ve predicates bölümlerinde nasıl tanımlanabileceği göreceğiz.

5.1. Basit veri nesneleri

Basit bir veri nesnesi, bir değişken veya bir sabitten oluşabilir. Sabit, constants bölümünde tanımlanan veri tipi değil; char, integer, symbol, string gibi değişmeyen bir nesnedir.

5.1.1 Veri Nesneleri Olan Değişkenler

VIP değişkenleri A-Z arasındaki büyük bir harf veya (_) ile başlamalıdır. (Değişken isimlerinde ç, İ, ö, ğ, ş vs. gibi Türkçe karakterler kesinlikle kullanılamaz) Yalnız başına kullanılan (_) değişkeninin anonim değişken olduğunu ve herhangi bir değerle eşleşebileceği bilinmektedir. Prolog’daki değişkenler global değil, lokaldir. Yani iki ayrı cümlecikte aynı isimle -örneğin X- gösterilen bir değişken farklıdır. Eşleşme sırasında birbiriyle eşleşebilir, fakat temelde birbiri üzerinde hiçbir etkisi yoktur.

5.1.2. Veri Nesneleri Olan Sabitler

Sabitler karakter, sayı veya atom biçiminde olabilirler.

5.1.3. Karakterler

Karakterler char kelimesi ile gösterilir ve 0-9, A-Z ve a-z, ASCII 127 karakter tablosundaki değerleri alabilirler. Fakat ASCII 32 (boşluk) ve daha küçük karakterler kontrol amacıyla kullanılırlar.

Tek karakterlik bir sabit şöyle yazılır:

‘a’ ‘3’       ‘*’       {       ‘W’      ‘A’      ‘\\’=\    ‘\’’=’    ‘\225’=b (ASCII 225)

Bunların dışında başka fonksiyonları olan karakterler de vardır.

‘\n’                       Yeni satıra geçiş komutu

‘\r’                        Satır sonu

‘\t’                        Yatay sekme (tab)

5.1.4. Sayılar

Sayılar tamsayı ve reel olabilirler. Reel sayılar 10-308-10+308arasında değişirler.

Örnek:

Tamsayılar                                   Reel Sayılar

10                                                3.

-77                                               34.96

32034                                          -32769

-10                                               4*10+27

5.1.5. Atomlar

Bir atom symbol veya string olabilir. İkisi arasındaki fark genelde Prolog’un çalıştırıldığı sisteme bağlıdır.

Prolog string ve symbol tipleri arasında otomatik dönüştürme yapabilir. Dolayısıyla symbol ve string tipindeki değişkenler birbirinin yerine kullanılabilirler. Fakat programcılıkta yaygın olan adet, çitf tırnak (“) içine alınması gereken sabitleri string, çitf tırnak gerektirmeyen sabitleri de symbol olarak kabul etmektir.

Symbol: küçük harfle başlayan ve sadece harf, rakam ve _ karakterlerini içerir.

String: Çift tırnak içine alınabilen ve string sonunu belirleyen 0 (Sıfır) hariç, herhangi bir karakteri içerebilir.

Symbol                 String

Yemek                             “Yavuz AYDIN”

Ahmetin_babasi   “ 12. Cadde”

A                          “a”

PdcProlog                        “Visual Prolog Development Center”

5.2. Bileşik Veri Nesneleri ve Fonksiyon Operatörleri

Bileşik veri nesneleri, birden fazla parçadan oluşan verileri tek bir parçaymış gibi kullanma imkanı tanır. Mesela 16 Mayıs 1998 tarihi gün, ay ve yıl olarak 3 parçadan oluşan bir bilgiyi temsil eder. Bu tür bir bilgiyi tek bir parçaymış gibi kullanmaya imkan tanıyan sabitler vardır.

Örnek:

Domains

Islem_tarihi= date(string, unsigned, unsigned)

şeklindeki bir tanımdan sonra D=date(“Mayıs”, 16, 1998) şeklinde yazılabilir. Burada D bir olgu değil, symbol veya sayı gibi kullanılabilecek bir veridir. Bu tür ifadeler genelde bir fonksiyon operatörü ve takip eden 3 argüman ile başlar. Operatörler herhangi bir hesaplama yapamazlar. Sadece bir tür bileşik veri nesnelerini tanımlar ve argümanların tek veriymiş gibi tutulmasına imkan tanır.

Bileşik veri nesnelerinin argümanları da bileşik olabilir. Örneğin

Dogum_Gunu

Kisi                                  date

“Ahmet”“SAGMEN”          “Mart” 15 1976

şeklinde gösterilen bir tarihi Prolog’da

dogum_tarihi(kisi(“Ahmet”,“SAGMEN”),date(“Mart”,15,1976)) şeklinde yazılabilir.

Bu örnekte dogum_tarihi bileşik nesnesinin iki bölümü vardır: kisi(“Ahmet”, “SAGMEN”) ve date(“Mart”, 15, 1976). Buradaki operatörler kisi ve date’dir.

5.3. Bileşik Nesnelerin Eşleştirilmesi

Bileşik bir nesne basit bir değişken veya kendisine uyan diğer bir bileşik nesne ile eşleşebilir. Örneğin date(“Nisan”, 18, 1983) şeklindeki bir clause tarih clause’una tam olarak eşleşir. Aynı şekilde date(“Nisan”, 18, 1983) date(Ay, Gun, Yil) cümleciğinde Ay=Nisan, Gun=18, Yil=1983 değerlerine atanır.

5.4. Bileşik Nesneleri Eşleştirmek İçin ‘=’ Sembolünün Kullanılması

VIP iki durumda eşleştirme işlemi yapar. İlki bir Goal veya çağrı fonksiyonunun bir cümleciğin baş kısmıyla eşleşmesi durumunda meydana gelir. İkincisi ise ‘=’ işaretinin argümanlar arasında kullanılması durumunda meydana gelir.

Prolog, eşitliğin her iki tarafındaki aynı işaretli nesneleri eşleştirmek için gerekli bağlantıları yapar. Aşağıdaki örnekte, soy isimleri aynı olan iki kişiyi bulup her ikisinin adreslerini eşitleyelim.

DOMAINS

sahis=sahis(isim, adres)

isim=isim(adi, soyadi)

adres=adres(cadde, sehir, ulke)

cadde=cadde(cadde_no, cadde_ismi)

sehir, ulke, cadde_ismi=string

adi, soyadi= string

cadde_no= integer

GOAL

P1 = sahis(isim(orhan, aydin), adres(cadde(5, “1. Cadde”), “Elazig”, “Turkiye”)),

P1= sahis(isim(_, aydin), Adres),

P2= sahis(isim(oktay, aydin), Adres),

write(“Birinci Sahis =”, P1), nl,

write(“İkinci Sahis =”, P2), nl.

5.5. Birden Fazla Nesneyi Tek Nesne Olarak Kullanmak

Prolog’da yazılmış programlarda bileşik nesneleri tek bir nesne gibi kullanmak kolaydır. Bu da programcılığı oldukça kolaylaştırır. Örneğin

sahiptir(fatih, kitap(“Visual Prolog İle Programlama”, “Prof.Dr. Asaf VAROL”)).

cümlesi ‘Fatihin Prof.Dr. Asaf Varol tarafından yazılan Visual Prolog ile Programlama adlı bir kitabı var’ anlamındadır. Benzer şekilde;

sahip(fatma, sevgili(can)).

cümlesi “Fatma’nın, ismi Can olan bir sevgilisi var” anlamına gelir. kitap(“Visual Prolog İle Programlama”, “Prof.Dr. Asaf VAROL”) ve sevgili(can) cümlelerdeki bileşik nesnelerdir.

Bileşik nesne kullanmanın önemli bir avantajı, birden fazla argümandan oluşan cümleleri sadece bir argüman olarak kullanabilmektir.

Örnek:

Basit bir telefon rehberi veritabanı programı

PREDICATES

Adres_listesi(symbol, symbol, symbol, symbol, integer, integer) /*(adi, soyadi, telefonu, ay, gun, yil)*/

clauses

adres_listesi(davut, yildirim, 3128456, ocak,6, 1978).

adres_listesi(maksut, hazneci, 3154878, nisan,14, 1969).

adres_listesi’ndeki 5 argümanı

sahis(Adi, Soyadi), dogum_tarihi(Ay,Gun,Yil).

şeklinde yazmak mümkündür. Yeni şekliyle program şöyle yazılabilir:

Domains

Adi= sahis(symbol, symbol)                                           /* (Adi, Soyadı)*/

Dogum_tarihi= d_tarihi(symbol, integer, integer)          /*(Ay, Gün, Yıl)*/

Telefon_no= symbol                                                       /* Telefon no*/

Predicates

adres_listesi(adi, telefon_no, d_tarihi)

clauses

adres_listesi(sahis(davut, yildirim), 3128456, d_tarihi(ocak,6, 1978)).

adres_listesi(sahis(maksut, hazneci), 3154878, d_tarihi(nisan,14, 1969)).

Şimdi yukarıdaki küçük programa birkaç kural daha ilave edip, doğum tarihleri bugünün tarihi ile uyuşanları bulmaya çalışalım. Programda standart yüklem olan date kullanılarak bugünün tarihi bilgisayardan alınacaktır.

DOMAINS

adi = sahis(symbol,symbol)                                            /* (Adı, Soyadı) */

dogum_gunu = d_gunu(symbol,integer,integer)                        /* (Ay, Gun, Yıl) */

tel_no = symbol                                                              /* Telefon Numarası */

PREDICATES

nondeterm tel_listesi(adi,symbol,dogum_gunu)

d_gunu_ayini_bul

ay_donustur(symbol,integer)

d_gunu_ayini_kontrol_et(integer,dogum_gunu)

sahsi_yaz(adi)

CLAUSES

d_gunu_ayini_bul:-

write(“====Bu ay doğanların listesi =======”),nl,

write(” Adı\t\t Soyadı\n”),

write(“==============================”),nl,

date(_, Bu_ay, _),                                   /* Tarihi bilgisayardan oku */

tel_listesi(Sahis, _, Date),

d_gunu_ayini_kontrol_et(Bu_ay, Date),

sahsi_yaz(Sahis),

fail.

d_gunu_ayini_bul:-

write(“\n\n Devam etmek için herhangi bir tuşa basın “),nl,

readchar(_).

sahsi_yaz(sahis(Adi,Soyadi)):-

write(” “,Adi,”\t\t “,Soyadi),nl.

d_gunu_ayini_kontrol_et(Yeni_ay,d_gunu(Ay,_,_)):-

ay_donustur(Ay,Ay1),

Yeni_ay = Ay1.

tel_listesi(sahis(paki, turgut), “267 78 41”, d_gunu(ocak, 3, 1965)).

tel_listesi(sahis(arif, gurel), “338 41 23”, d_gunu(subat, 5, 1972)).

tel_listesi(sahis(mehmet_can, hallac), “512 56 53”, d_gunu(mart, 3, 1965)).

tel_listesi(sahis(cuma, cetiner), “267 22 23”, d_gunu(nisan, 29, 1963)).

tel_listesi(sahis(omer, akgobek), “355 12 12”, d_gunu(mayis, 12, 1971)).

tel_listesi(sahis(fatih, dilekoglu), “438 63 42”, d_gunu(haziran, 17, 1970)).

tel_listesi(sahis(levent, aksun), “567 84 63”, d_gunu(haziran, 20, 1972)).

tel_listesi(sahis(cengiz, gok), “255 56 53”, d_gunu(temmuz, 16, 1973)).

tel_listesi(sahis(kasim, yenigun), “132 22 23”, d_gunu(agustos, 10, 1968)).

tel_listesi(sahis(husamettin, bulut), “412 48 34”, d_gunu(eylul, 25, 1967)).

tel_listesi(sahis(arif, demir), “315 24 21”, d_gunu(ekim, 20, 1992)).

tel_listesi(sahis(sezen, demir), “233 13 12”, d_gunu(kasim, 9, 1980)).

tel_listesi(sahis(nebahat, arslan), “337 22 23”, d_gunu(kasim, 15, 1987)).

tel_listesi(sahis(leyla, aydin), “145 41 50”, d_gunu(aralik, 24, 1940)).

ay_donustur(ocak, 1).

ay_donustur(subat, 2).

ay_donustur(mart, 3).

ay_donustur(nisan, 4).

ay_donustur(mayis, 5).

ay_donustur(haziran, 6).

ay_donustur(temmuz, 7).

ay_donustur(agustos, 8).

ay_donustur(eylul, 9).

ay_donustur(ekim, 10).

ay_donustur(kasim, 11).

ay_donustur(aralik, 12).

GOAL d_gunu_ayini_bul.

Yukarıdaki program kodu incelendiğinde, bileşik nesnelerin neden faydalı olduğu açıkça görülür. Dogum_tarihi_ayi yüklemi, en yoğun kullanılan cümle durumdadır.

1.     Program ilk önce sonuçları bir pencerede görüntüler.

2.     Sonra sonuçların yorumlanacağı bir başlık kısmı görüntülenir.

3.     Daha sonra hazır fonksiyonlardan biri olan date kullanılarak bilgisayarın saatinden bugünkü tarih okunur ve ay belirlenir.

4.     Bundan sonra yapılması gereken tek şey, satırlar halinde sıralanan veritabanından isim, telefon no, doğum tarihi okutmaktır. Burada doğum tarihi sistemden okunan ay ile karşılaştırılıp aynı olanlar bulunur. adres_listesi(Sahis,_, Date) çağrısı adı ve soyadını Sahis değişkenine atar ve sahis operatörü Sahis’a atanmış olur. Date değişkeni de ilgili şahsın doğum tarihini alır. Buradaki adres_listesi bileşik bir değişken olup, bir kişi hakkındaki bütün bilgileri saklar.

5.     Daha sonra aranan kişinin doğum tarihini Date değişkenine atar. Bir sonraki alt hedefte tamsayıyla gösterilen bugünkü ay ve kişinin doğum tarihi dogum_gununu_kontrol_et yüklemine iletilir.

6.     dogum_gununu_kontrol_et yüklemi iki değişkenle birlikte çağrılır. İlk değişken bir tamsayıya, ikincisi ise dogum_tarihi’ne bağlanır. dogum_gununu_kontrol_et kuralını tanımlayan kuralın baş kısmındaki Bu_ay Mon değişkenine atanır. İkinci argüman olan Date ise dogum_tarihi(Ay, _,_) cümleciğine atanır. Sadece bugünkü tarihten ay ile ilgilendiğimiz için, gün ve yıl için anonim değişkenler kullanılmıştır.

7.     dogum_gununu_kontrol_et yüklemi ayın sembolik değerini tam sayıya dönüştürür ve bu değeri sistemden okunan ay değeri ile karşılaştırır. Karşılaştırmanın başarılı olması durumunda bir sonraki alt hedefe geçer. Karşılaştırma başarısız olursa geriye iz sürme işlemi başlar.

8.     İşlenmesi gereken bir sonraki alt hedef adini_yaz’dır. İstenilen bilgi, doğum tarihi bu ay olan kişinin ismi olduğu için, ekrana bu kişinin adı ve soyadı yazılır. Bir sonraki cümle ‘fail’ olduğu için otomatik olarak geriye iz sürme işlemi başlar.

9.     Geriye iz sürme daima en son kullanılan non-deterministic yükleme geri gider. Bizim programımızda zaten non-deterministic bir yüklem bulunduğu için işlem hemen adres_listesi’ne gider. Program burada işleme konmak üzere başka bir isim aramak için veritabanına gider. Eğer veritabanında işleme konacak başka biri kişi yoksa işlemdeki cümle başarısız olur. Prolog, veritabanındaki diğer satırları inceler ve dogum_gununu_kontrol_et kuralını tanımlayan başka bir cümle bulur.

5.6. Bileşik Nesnelerin Tiplerini Tanımlamak

Bu bölümde bileşik nesne tiplerinin nasıl tanımlanacağı üzerinde duralım.

sahiptir(ahmet, kitap(“Pascal 7”, “Ömer AKGÖBEK”))

sahiptir(ahmet, at(firtina)).

şeklinde tanımlanan ilişkiler

GOAL sahiptir(ahmet, Ne)

sorgusuyla irdelenirse Ne değişkeni iki ayrı argüman ile eşleşebilir. Bunlardani biri kitap, diğeri ise at’tır. Sahiptir yüklemi artık sahiptir(symbol, symbol) şeklinde tanımlanamaz. İkinci argüman symbol tipindeki nesnelere işaret etmez. Bunun yerine

sahiptir(isim, esyalar)

şeklinde bir yüklem tanımlamak mümkündür. Tipleri tanımlarken

Domains

esyalar= kitap(kitap_adi, yazar); at(atin_adi)

kitap_adi, yazar, atin_adi            = symbol

yazılabilir.

Yukarıdaki ‘;’ işareti ‘veya’ anlamına gelir. Bu durumda iki alternatiften bahsetmek mümkündür. Bir kitap, kitabın adı ve yazarının adıyla, bir ‘at’ ise sadece ismiyle tanımlanabilir. Kitap_adi, yazar, atin_adi değişkenlerinin tamamı symbol tipindedir.

Tip tanımlanmasına daha fazla alternatif rahatlıkla ilave edilebilir.

Örnek

DOMAINS

esyalar= kitap(kitap_ismi, yazar); at(atin_adi); araba; banka_hesabi(nakit)

kitap_ismi, yazar, atin_adi=symbol

nakit         = real

isim=symbol

PREDICATES

nondeterm sahiptir(isim, esyalar)

CLAUSES

sahiptir(ahmet, kitap(“Pascal 7.0”, “Ömer AKGÖBEK”)).

sahiptir(ahmet, at(firtina)).

sahiptir(ahmet, araba).

sahiptir(ahmet, banka_hesabi(1000)).

GOAL sahiptir(Kim, Sahip_oldugu_esyalar).

Programı derlenip çalıştırıldığında

Sahip_oldugu_esyalar=kitap(“Pascal 7.0”, “Ömer AKGÖBEK”)).

Sahip_oldugu_esyalar=at(firtina)).

Sahip_oldugu_esyalar=araba

Sahip_oldugu_esyalar= banka_hesabi(1000)).

4 Solutions

cevabı görüntülenir.

5.7. Tip Tanımlamaları Üzerine Kısa Bir Özet

Bileşik nesnelerin tip tanımları genel bir şekilde gösterilecek olursa:

Domain= alternatif1(Tip, Tip, ……); alternatif2(Tip, Tip, …..)

Burada alternatif1 ve alternatif2 farklı operatörlerdir. (Tip, Tip, …) gösterimi standart veya başka yerde ayrıca tanımlanan symbol, integer, real vs. gibi tip isimlerdir.

Not:

1.     Alternatifler birbirinden daima ‘;’ ile ayrılırlar.

2.     Her alternatif bir operatör ve bu argümana ait tip tanımlarını içerir.

3.     Eğer operatörde herhangi bir argüman kullanılmazsa, bunu alternatifN veya alternatifN( ) biçiminde yazılabilir.

5.8. Çoklu-Düzey Bileşik Nesneler

Prolog’da, birden fazla dereceden oluşan bileşik nesne kullanmak mümkündür. Örneğin

kitap(“Atatürk: Bir Milletin Yeniden Doğuşu”, “Kinross”)

olgusundaki ‘Kinross’ soyismi yerine, yazarın adını ve soyadını ayrıntılı olarak gösteren bir yapı kullanmak mümkündür.

kitap(“Atatürk: Bir Milletin Yeniden Doğuşu”, yazar(“Lord”, “Kinross”))

Daha önceden yapılan tip tanımında

kitap(kitap_adi, yazar)

yazılıyordu. İkinci argüman olan yazar, operatör durumundadır. Fakat yazar=symbol sadece bir isimi kapsadığından, yetersiz kalır. Bu durumda yazar değişkeninin de bileşik nesne olarak tanımlanması gerekir. Bunu da:

yazar=yazar(isim, soyisim)

şeklinde tanımlamak mümkündür. Şimdi tip tanımlarına geçelim.

Domains

esyalar=kitap(kitap_adi, yazar);              /*İlk derece*/

yazar=yazar(adi, soyadi)                         /*ikinci derece*/

kitap_adi, isim, soyisim=symbol             /*Üçüncü derece*/

Birden fazla dereceden oluşan bileşik nesneler kullanırken, ağaç biçiminde bir yapı kullanmak büyük kolaylık sağlar.

Kitap

Kitap_adi             yazar

                               İsim,       soyisim

Tip tanımı yapılırken bir anda ağaç yapısının sadece bir derecesi kullanılabilir. Örneğin

kitap=kitap(kitap_adi, yazar(adi, soyadi))

Şeklindeki tip tanımı yanlıştır.

5.9. Çoklu-Tipli Argümanlar

Bir yüklemin farklı tiplerde bilgi verebilmesi için bir operatör tanımının yapılması yapmamız gerekir. Aşağıdaki örnekte sizin_yasiniz cümleciği yas argümanını kabul edilmektedir. Yas argümanı ise string, real veya integer olabilir.

DomaIns

yas=i(integer); r(real); s(string)

Predicates

siniz_yasiniz(yas)

CLAUSES

sizin_yasiniz(i(Yas)):-write(Yas).

sizin_yasiniz(r(Yas)):-write(Yas).

sizin_yasiniz(s(Yas)):-write(Yas).

5.10. Listeler

Öğretim üyelerinin verdikleri dersleri liste halinde saklamak istediğimizi kabul edelim. Bunun için aşağıdaki kodun yazılması yeterlidir.

PREDICATES

profesor(symbol, symbol,symbol) /*Adı, soyadı ve verdiği ders*/

CLAUSES

profesor(asaf, varol, bilgisayar).

profesor(ali, erdogan, betonarme).

profesor(ahmet, aydogan, fizik).

Bu tür bir programda, bütün hocaların isimlerini ve verdikleri dersleri tek tek sıralamak mümkündür. Her hoca için ayrı bir olguyu veritabanına ilave etmek gerekir. Kolay görünen bu işin yüzlerce öğretim üyesi olan bir üniversite için yapıldığında ne kadar zor olduğunu açıktır. Prolog’daki liste bir veya daha fazla değer alabilir ve benzer işlerde büyük kolaylıklar sağlar. Bir listedeki değişkenlerin aldıkları değerleri ‘[]’ arasında yazmak gerekir.

DOMAINS

dersler=symbol*

PREDICATES

profesor(symbol, symbol, dersler)

CLAUSES

profesor(asaf, varol, [bilgisayar, termodinamik, iklimlendirme]).

profesor(ali, erdogan, [betonarme, statik, malzeme]).

profesor(ahmet, aydogan, (fizik, matematik, kimya]).

Şeklindeki satırlarda dersler liste tipinde bir değişken olarak tanımlanmıştır. Buradaki ‘*’ sembolü dersler değişkeninin liste tipinde olacağını gösterir. Aynı biçimde, listenin tamsayılardan oluştuğu bir değişken tipi

Domains

tamsayilar_listesi=integer*

şeklinde tanımlanabilir.

Örnek:

DOMAINS

notlar=integer*

PREDICATES

nondeterm sinav_sonuclari(symbol, symbol, notlar)

CLAUSES

sinav_sonuclari(orhan, aydin, [78, 98, 100]).

sinav_sonuclari(kasim, yenigun, [45, 54, 60]).

sinav_sonuclari(husamettin, bulut, [80, 90, 95]).

sinav_sonuclari(huseyin, karasu, []).

GOAL sinav_sonuclari(orhan,_,Aldigi_Notlar).

Programı çalıştırıldığında Orhan’ın aldığı notlar görüntülenir.


6. TEKRARLAMA VE REKÜRSİYON

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.

6.1. 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.

6.2. 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.

6.3. Ö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. İkinci 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.

GOAL ulke_adlarini_yaz.

İ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 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.

6.4. 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.

Harf’e değer atayan readchar(Harf) yükleminin öncesine geriye dönüş yapıldığı anda, Harf değişkeni serbest hale gelir. Değişken değerinin kaybolması geriye dönüş işlemi sayesinde alternatif çözümler elde etmek için çok önemlidir. Fakat geriye dönüş işlemi başka bir iş için kullanılamaz. Çünkü geriye dönüş işlemi alternatif ararken, işlemleri birçok kez tekrar edebilir. Fakat bu tekrarlar sırasında bir tekrardan diğerine geçişte hiçbir şey hatırlayamaz. Daha önce de söylediğimiz gibi, işlemlerden sonra değer ataması yapılan değişkenlerin tamamı, geriye dönüş işlemi sırasında bütün bu değerleri kaybederler. Böyle bir döngüde sayaç gibi bir şey kullanıp toplam, kayıt sayısı vs. gibi bir değeri tutmanın kolay bir yolu yoktur.

6.5. Rekursif Prosedürler

Tekrarlama işlemin yapmanın diğer bir yolu da rekursiyondur. Kendisini çağırabilen prosedüre rekursiv prosedür diyoruz. Rekursiv prosedürler çalışırken yaptıkları işlerin sayısını, toplamını veya işlemlerin ara sonuçlarını saklayabilir ve bunları bir döngüden diğerine rahatlıkla aktarabilirler.

Örnek:

N sayısının faktoriyelini hesaplamak için

1. Eğer N=1 ise, faktoriyel=1

2. Diğer durumlarda N-1’in faktoriyelini bul ve bunu N ile çarp

şeklindeki emirleri anlayıp uygulayan bir program yazalım. Örneğin 3 sayısının faktöriyelini bulmak için 2’nin faktöriyelini, 2’nin faktöriyelini bulmak için de 1’in faktöriyelini bulmamız gerekir. 1’in faktöryeli zaten bilindiğinden yapılması gereken tek şey 2 ve 1’in faktöriyellerini N sayısı olan 3 ile çarpmaktır. Görüldüğü gibi işlemler burada sonsuza kadar gitmemektedir. Şimdi bunları Prolog ile ifade etmeye çalışalım:

PREDICATES

faktoriyel(unsigned, real)

CLAUSES

faktoriyel (1, 1):-!.

faktoriyel (X, Faktoriyel_X):-

Y=X-1,

faktoriyel(Y, Faktoriyal_Y),

Faktoriyel_X=X*Faktoriyal_Y.

GOAL X=6, faktoriyel(X, Faktoriyel).

Programı 6 sayısının faktöriyelini bulur.

Burada ilginç bir durum vardır. Bilgisayar faktöriyel işleminin yarısında iken nasıl olur da faktöriyeli hesaplar? Faktöriyel kuralını X=6 olacak şekilde çağırılırsa, faktöriyel kendini X=5 için çağırılacaktır. Bu durumda X değeri 6 mı olacak 5 mi?

Cevap şudur: Bilgisayar faktöriyel prosedürünün bir kopyasını oluşturur ve bu kopyayı çağır. Kopyanın kendini faktoriyel prosedürünün aynısıymış gibi çalışır. Sadece argümanların ve değişkenlerin kopyalarına ihtiyaç duyulur.

Bu bilgi yığın olarak hafızada saklanır ve bir kural çağrıldığında her seferinde yeniden oluşturulur. Kural non-deterministic değil ise sona erdiği zaman bellek yığını sıfırlanır.

6.5.1. Rekursiyonun Avantajları

·       Başka türlü güvenli bir şekilde ifade edilemeyen algoritmaları daha açık bir şekilde ifade edebilir.

·       Mantıksal olarak iterasyondan çok daha basittir.

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

Rekursiyon işlemi özellikle problem 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.

6.5.2. Sondan Rekursiyon 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 prosedürün yapması gereken işlem bittiği zaman, çağrıyı yapan prosedür kaldığı yerden işleme devam edebilir. Bunun dezavantajı şudur: Örneğin bir prosedür kendisini 100 defa çağırırsa, her seferki durum kaydedileceği için tam olarak 100 değişik durum hafızaya alınmış olur. Hafızaya alınan her duruma stack frame (yığın alanı) denir. 16 bitlik PC DOS sisteminde bu alan 64K ile sınırlı olup, ancak 3000-4000 yığın alacak kapasitedir. 32 Bit’lik sistemlerde teorik olarak bu alan GB düzeyine kadar çıkabilir, fakat bu kez de başka engeller ortaya çıkar. Yığın alanın azaltmak için ne yapılabilir?

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 şey kalmaz. Çağrıyı yapan prosedürün kendisin ç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.

B prosedürünün C’yi çağırmak yerine, kendisini işlemin en son adımı olarak çağırdığını düşünelim. B prosedürü yine B’yi çağırdığı zaman, çağrıyı yapan B’nin yığın bilgisi, çağrılan B’nin yığın bilgisi ile yer değiştirilmelidir. Bu ise çok basit bir işlemden yani argümanların yeni değerleri almasından ibarettir. Daha sonra işlem, prosedürün baş kısmına gider. Prosedürel olarak bu olay bir döngüdeki kontrol değerlerinin yenilenmesine benzer.

Bu işlemlere sondan rekursiyon optimizasyonu veya son-çağrı optimizasyonu adı verilmektedir.

6.5.3. Sondan Rekursiyonun Kullanımı

Prologda bir prosedürün başka bir prosedürü ‘kendisinin en son adımı olarak çağırmasının’ ne anlama geldi konusu incelenecektir.

1.     Çağrı, cümlenin en son alt hedefidir.

2.     Bu cümlenin ilk kısımlarında geriye dönüş noktaları yoktur.

Aşağıdaki örnek bu iki şartı sağlamaktadır:

sayac(Sayi):-

write(Sayi), nl,

yeni_sayi=Sayi+1,

sayac(Yeni_sayi).

İşte bu prosedür sondan rekursif bir prosedürdür ve hafızada yeni bir yığına neden olmaksızın kendisini çağırır. Dolayısıyla hafızayı tüketmez. Bu programa GOAL sayac(0) değeri verilse, 0 ile başlayan tam sayılar yazılmaya başlanır ve işlem bitmez.

Örnek:

PREDICATES

sayac(ulong)

CLAUSES

sayac(Sayi):-

write(‘\r’,Sayi),

Yeni_sayi=Sayi+1,

sayac(Yeni_sayi).

GOAL nl, sayac(0).

Bu programa GOAL sayac(0) ile çalıştırılırsa, 0’dan başlamak üzere tam sayılar yazılmaya başlanır ve işlem bitmez.

6.5.3. Sondan Rekursiyonu Engelleme

1.     Eğer rekursiv çağrı en son adım değilse, prosedür sondan rekursiv değildir. Örneğin;

PREDICATES

rakam_yaz(ulong)

CLAUSES

rakam_yaz(Sayi):-

write(‘\r’, Sayi),

Yeni_sayi=Sayi+1,

rakam_yaz(Yeni_sayi), nl.

Goal nl, rakam_yaz(0).

Rakam_say prosedürü kendisini çağırdında, kontrolün yeniden rakam_say(Sayi) dönmesi için hafızada bir yığın kaydedilir. Çünkü son alt hedef ‘nl’dir ve bunun işleme girmesi gerekir. Dolayısıyla döngü bir süre sonra hata mesajı vererek durur.

2. Sondan rekursiyonu engellemenin bir diğer yolu da rekursiyonun yapıldığı anda, geriye henüz denenmemiş bir alternatifin kalmasıdır. Bu durumda prosedürün son durumunun kaydedilmesi gerekir. Çünkü rekürsiv çağrının başarısız olması durumunda çağrıyı yapan prosedürün geriye gidip denenmemiş bir alternatifi deneyebilmesi gerekir.

Örnek:

Clauses

rakam_yaz(Sayi):-

write(‘\r’, Sayi),

Yeni_sayi=Sayi+1,

rakam_yaz(Yeni_sayi).

rakam_yaz(Sayi):-

Sayi<0, write(“Sayi sıfırdan küçüktür.”).

GOAL rakam_yaz(0).

Burada rakam_yaz cümlesi, ikinci cümle denenmeden önce kendisini çağırır. Program yine bir süre sonra hafıza tükenmesinden dolayı durur.

1.     Denenmemiş alternatifin rekursiv prosedürün kendisi için ayrı bir cümle olması gerekmez. Rekürsiv prosedürün çağırdığı başka bir cümlede bir alternatif de olabilir. Örnek:

rakam_yaz(Sayi):-

write(‘\r’, Sayi),

Yeni_sayi=Sayi+1,

Kontrol_et(Yeni_sayi).

rakam_yaz(Yeni_sayi).

Kontrol_et (Z):-Z>=0.

Kontrol_et (Z):- Z<0.

Sayi değişkeninin değeri normalde pozitiftir. Bu durumda rakam_yaz her ne zaman kendisini çağırsa, kontrol_et yükleminin ilki doğrulanır, fakat ikinci kontrol_et yüklemin henüz doğrulanmamış durumdadır. Bu yüzden rakam_yaz yüklemi, geriye dönüş işlemi sırasında kontrol etmek üzere yığın bölgesine bir kopya almak zorundadır.

PREDICATES

yanlis_sayac1(long)

yanlis_sayac2(long)

yanlis_sayac3(long)

kontrol_et(long)

CLAUSES

/* Rakam_yaz: Rekursiv çağrı son adım değildir.*/

yanlis_sayac1(Sayi):-

write (‘\r’, Sayi),

Yeni_sayi=Sayi+1,

yanlis_sayac1(Yeni_sayi), nl.

/* Rakam_yaz2: Rekursiv çağrı yapıldığı anda henüz denenmemiş bir clause var.*/

yanlis_sayac2(Sayi):-

write (‘\r’, Sayi),

Yeni_sayi=Sayi+1,

yanlis_sayac2(Yeni_sayi).

yanlis_sayac2(Sayi):-

Sayi<0,

write (“Sayı negatiftir.”).

/* Rakam_yaz3: Rekursiv çağrıdan önce çağrılan yüklemde denenmemiş bir alternatif var.*/

yanlis_sayac3(Sayi):-

write (‘\r’, Sayi),

Yeni_sayi=Sayi+1,

kontrol_et(Yeni_sayi),

yanlis_sayac3(Yeni_sayi).

kontrol_et(Z):-

Z>=0.

kontrol_et(Z):-

Z<0.

GOAL yanlis_sayac1(1458).

6.6. Rekursiyonda Cut Kullanımı

Bir prosedürün sondan rekürsiyonlu olup olmadığından kesin olarak emin olunamayacağı düşünülebilir. Rekursiv olan çağrıyı, son cümleciğin en son alt hedefi yaparak, bu problemi çözmek mümkündür. Fakat yine de hedef cümlesinin çağıracağı diğer prosedürler arasında denenmemiş başka bir alternatif olmadığını nasıl garantiye alabiliriz?

Bunu garantiye almak gerekmez, çünkü ‘!’ yani Cut komutunu kullanarak bulunabilecek bütün alternatiflerin önünü kesmek mümkündür. Cut komutunun anlamı şöyleydi. Goal cümleciği ile belirlenen yükleme uygun çözüm ararken, gelinen nokta bizim aradığımız noktadır. Artık öteye gitmeye gerek bulunmamaktadır. Alternatifler de ortadan kalktığı için, artık hafızada yığın oluşturmaya gerek kalmaz.

Yukarıdaki örnekte görülen rakam_say cümlesini, şöyle düzeltmek mümkündür (İşlemdeki ismini değiştirelim):

Cut_sayaci3(Sayi):-

Write (‘\r’, Sayi),

Yeni_sayi=sayi+1,

Kontrol_et(Yeni_sayi), !, cut_sayaci3(Yeni_sayi).

Cut komutu yanlis_sayac2 cümleciğinde aynı şekilde etkili olur. Çünkü testi negatife düşürüp ikinci cümlecikten birinciye taşır.

Cut_sayaci2(Sayi):-

Sayi>=0, !,

Write (‘\r’, Sayi),

Yeni_sayi=Sayi+1,

cut_sayaci2(Yeni_sayi).

Cut_sayaci2(Sayi):- write (“Sayi negatiftir.”).

Cut komutunu non-deterministic olan, yani birden fazla çözüm sağlayan yüklemlerle çalışırken, alınan bilginin yeterli olduğuna inanıldığı anda rahatlıkla kullanılabilir.

Aynı şey yanlis_sayac3 için de geçerlidir. Kontrol_et yüklemi işaretine bağlı olarak Sayi üzerinde biraz daha işlem yapılmasını gerektiren bir durumu göstermektedir. Fakat kontrol_et kodu non-deterministic olduğu için Cut komutu iyi bir çözümdür. Kontrol_et yüklemi şöyle yazılabilir:

Kontrol_et(Z):- Z>=0, !, /* Z’yi kullanarak işlem yapmak*/

Kontrol_et(Z):-……

Cut komutu kullanıldığı zaman bilgisayar denenmemiş bir alternatifin kalmadığına karar verir ve bu yüzden de yığın oluşturma yoluna gitmez. Aşağıdaki programda yanlis_sayac2 ve yanlis_sayac3’ün düzeltilmiş hali vardır.

PREDICATES

cut_sayaci2(long)

cut_sayaci3(long)

nondeterm kontrol_et(long)

CLAUSES

/* Rekursiv çağrı yapıldığı anda henüz denenmemiş bir seçenek var*/

cut_sayaci2(Sayi):-

Sayi>=0, !,

write(‘\r’, Sayi),

Yeni_sayi=Sayi+1,

cut_sayaci2(Yeni_sayi).

cut_sayaci2(_):-

write(“Sayi negatiftir.”).

/* Rekursiv çağrıdan önceki cümlecikte henüz denenmemiş bir seçenek var*/

cut_sayaci3(Sayi):- write(‘\r’, Sayi),

Yeni_sayi=Sayi+1,

kontrol_et(Yeni_sayi), !, cut_sayaci3(Yeni_sayi).

kontrol_et(Z):-Z>=0.

kontrol_et(Z):-Z<0.

GOAL cut_sayaci3(214).

6.7. Argümanların Döngü Değişkeni Olarak Kullanımı

Rukursiyon bölümünde verilen bir sayının faktöriyelini hesaplayan bir program geliştirilmişti. Bu durum Pascal’da şöyle ifade edilebilir:

P:=1;

For I:=1 to N do P:= P*I;

FactN:=P;

N, faktöriyeli hesaplancak olan sayı, FactN, N sayısının faktöriyeli, I değeri 1’den N’e kadar değişen döngü değişkeni ve P ise ara sayıların değerlerinin toplandığı değişkendir.

Bu programı Prolog’a aktarırken yapılması gereken ilk şey, for komutu için daha basit bir döngü kurmak ve her adımda I değişkenine ne olduğunu daha açık şekilde göstermektir. Program while ile, aşağıdaki biçimde yazılır.

P:=1;                    /* P ve I değişkenlerine ilk değeri ata.*/

I:=1;

While I<= N do               /* Döngü kontrolü*/

Begin

P:=P*I;                 /*P ve I değişkenlerine yeni değerleri ata*/

I:=I+1;

End;

FactN:=P;            /* Sayının Faktöriyelini Yaz..*/

Aynı program Prolog ile aşağıdaki gibi yazılır.

PREDICATES

faktoriyel(unsigned, real)

carpanlarin_faktoriyeli(unsigned, long, unsigned, long)

CLAUSES

faktoriyel(Sayi, Sayinin_Faktoriyeli):-

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, 1, 1).

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, I,P):-

I<=Sayi, !,

Yeni_P=P*I,

Yeni_I=I+1,

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, Yeni_I, Yeni_P).

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, I, P):-

I>Sayi,

Sayinin_faktoriyeli=P.

GOAL faktoriyel(5, Sayinin_Faktoriyeli).

Programın ayrıntıları aşağıda verilmiştir.

Faktoriyel cümleciğinin Sayi ve Sayinin_Faktoriyeli olmak üzere iki değişkeni vardır. Bunlardan sayı, faktöriyeli bulunacak sayı, diğeri ise bu sayının faktöriyelidir. Rekursiyon işlemi aslında carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, I, P) cümlesinden meydana gelir. Bu cümledeki 4 değişkenin bir adımdan diğerine aktarılması zorunludur. Bu yüzden faktoriyel sadece carpanlarin_faktoriyeli yüklemini harekete geçirir ve sayı, sayının faktöriyeli, I ve P’nin ilk değerlerini buraya aktarır. Böylece

faktoriyel(Sayi, Sayinin_Faktoriyeli):-

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, 1, 1).

sayesinde I ve P değişkenleri ilk değerlerini almış olurlar. Burada dikkat çeken şey, faktoriyel yükleminin hiçbir değeri olmayan Sayinin_faktoriyeli değerini carpanlarin_faktoriyeli yüklemindeki sayinin_faktoriyeli değişkenine aktarmasıdır. Prologun yaptığı tek şey, iki cümlede bulunan Sayinin_faktoriyeli değişkenlerini eşleştirmektir. Aynı şey carpanlarin_faktoriyel’i yüklemindeki sayinin_faktoriyeli değişkeninin rekursiv çağrı esnasında kendisine atanmasında da olur. Son aşamada ise Sayinin_faktoriyeli bir değer alacaktır. Bu değeri aldığı zaman daha önceki bütün sayinin_faktoriyeli değişkeni aynı değeri alır. Gerçekte ise sayinin_faktoriyeli değişkeninin bir değeri vardır. Çünkü Sayinin_faktoriyeli değişkeni, ikinci cümledeki carpanlarin_faktoriyeli cümlesinden önce hiçbir zaman gerçek anlamda kullanılmaz.

Şimdi carpanlarin_faktoriyeli yüklemine gelelim. Bu yüklem, döngünün devam şartı olan I sayısının Sayi’dan az veya eşit olup olmadığını kontrol eder. Daha sonra Yeni_I ve Yeni_P değerleriyle kendisini rekursiv olarak çağırır. Burada Prolog’un başka bir özelliği ortaya çıkmaktadır. Diğer dillerin çoğunda mevcut olan

P=P+1

şeklindeki bir ifade Prolog’da yanlıştır. Bu yüzden Prolog’da bir değişkenin değerini değiştirmek mümkün değildir. Bunun yerine

Yeni_P=P+1

şeklinde bir ifade kullanmak gerekir. Bu durumda ilk cümlecik

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, I,P):-

I<=Sayi, !,

Yeni_P=P*I,

Yeni_I=I+1,

carpanlarin_faktoriyeli(Sayi, Sayinin_Faktoriyeli, Yeni_I, Yeni_P).

şeklinde yazılabilir. Buradaki Cut komutu, cümlecik yüklemde en sonda olmasa da, son çağrı optimizasyonuna imkan tanır. Zamanla I değişkeninin değeri Sayi değişkeninin değerine geçer. Bu durumda işlem P’nin o anki değerini sayinin_faktoriyeli ile eşleştirir ve rekursiyonu bitirir. Bu nokta ikinci cümlede, yani birinci cümledeki I<=Sayi testinin yanlış çıktığı zaman meydana gelecektir.

carpanlarin_faktoriyeli(Sayi, Sayinin_faktoriyeli, I, P):- I>Sayi, sayinin_faktoriyeli=P.

haline dönüşür. Sayinin_faktoriyeli=P ifadesinin ayrı bir satırda olması gerekmez. Çünkü sayinin_faktoriyeli değişkeninin yerine P değişkenini yazarak değer ataması yapılabilir. Ayrıca I>Sayi testi de gereksizdir, çünkü bunun tersi zaten birinci cümlede denenmiş olmaktadır. Bunun son hali:

carpanlarin_faktoriyeli(_, Sayinin_faktoriyeli,_, Sayinin_Faktoriyeli) olur.

PREDICATES

faktoriyel(unsigned,real)

faktoriyel(unsigned,real,unsigned,real)

CLAUSES

faktoriyel(Sayi, Sayinin_faktoriyeli):-

faktoriyel(Sayi, Sayinin_faktoriyeli,1,1).

faktoriyel(Sayi, Sayinin_faktoriyeli, Sayi, Sayinin_faktoriyeli):-!.

faktoriyel(Sayi, Sayinin_faktoriyeli,I,P):-

Yeni_I = I+1,

Yeni_P = P*Yeni_I,

faktoriyel(Sayi, Sayinin_faktoriyeli, Yeni_I, Yeni_P).

GOAL faktoriyel(12, Sayinin_Faktoriyeli).

6.8. Rekursiv Veri Yapıları

Sadece kurallar değil, aynı zamanda veri yapıları da rekursiv olabilir. Prolog bu tür yapıların kullanılmasına imkan tanıyan yaygın kullanılan tek programlama dilidir. Bir veri türü, kendisi gibi yapıları içeren başka yapıların kullanımına izin veriyorsa, bu tür veri tiplerine rekursiv denir. En temel rekursiv veri türü listelerdir. Fakat ilk bakışta rekursiv yapıda oldukları belli olmaz.

Şimdi rekursiv olan bir veri türü tanımlayıp, bunu oldukça hızlı bir sıralama programında kullanılması gösterilecektir. Bu veri türünün yapısı aşağıda ağaç yapısında verilmiştir. Görüldüğü gibi Ali ve Ayşe ile gösterilen her bir dal kendi içinde ayrıca alt dallara ayrılmıştır. Bundan dolayı da bu tür bir yapı rekursiv olarak adlandırılır.

Şekil 6.1. Aile Fertlerinin Şecere Olarak Gösterilmesi

6.9. Ağaç Biçimindeki Veri Türleri

Rekursiv veri türleri, ALGOL60 dilinden Pascal dilini çıkaran Niklaus Wirth tarafından popüler hale getirilmiştir. Bu veri tiplerini Pascal’da kullanmamış, fakat faydalarına değinmiştir.

Visual Prolog, otomatik olarak oluşturulup, pointerlar içereren gerçek rekursiv tip tanımlara imkan tanır. Örneğin aşağıdaki biçimde bir ağaç yapısı tanımlamak mümkündür.

Domains

Agac_yapisi= agac(string, agac_yapisi, agac_yapisi)

Bu ifade agac isimli bir operatör tanımlandığını, bunun da biri string, ikisi ayrıca ağac yapısında, toplam üç değişkeninin olduğunu gösterir.

Ağaç yapısındaki hiçbir veri türü sonsuza kadar gidemeyeceği, rekursiyonu da bitirmek mümkün olmadığı için bu ifade tam olarak doğru değildir. Örneğin bazı hücrelerin diğer hücrelerle bağlantıları yoktur. Prolog’da ağaç yapısındaki bir veri yapısında iki tip operatör tanımlanır. Bunlar üç ayrı argümanı olan agac veya hiçbir argümanı olmayan bos operatörleridir.

Domains

Agac_yapisi= agac(string, agac_yapisi, agac_yapisi); bos

Yukarıdaki agac ve bos adındaki yüklemlerin Prolog’da önceden tanımlı bir anlamları yoktur ve programcı bunların yerine istediği başka isimleri kullanabilir. Şimdi Şekil 6.1’de gösterilen tablonun Prolog’da nasıl ifade edilebileceği incelenecektir.

agac(“Emine”, agac(“Ali”, agac(“Hasan”, bos, bos) agac(“Fatma”, bos, bos))

agac(“Ayşe”, agac(“Fuat”, bos, bos) agac(“Leyla”, bos, bos)))

6.9.1. Bir Ağaç Yapısında Tarama Yapma

Ağaç şeklindeki yapılarda yoğun olarak yapılan işlem, ya bütün hücreleri incelemek ve hücreleri bir şekilde işlemek veya belirli bir değeri aramak ve bütün değerleri toplamaktır. Buna bir ağacı taramak adı verilmektedir.

Bunun en temel algoritmalarından biri şudur:

2.     Eğer ağaç boş ise hiçbir şey yapma

3.     Eğer dolu ise, o anki noktayı incele, buradan soldaki alt dala geç ve daha sonra sağdaki alt dalı incele.

Algoritma da tıpkı ağaç yapısı gibi rekursivdir. Soldaki ve sağdaki ağaç yapılarını orijinal ağaç gibi inceler. Prolog bunu iki cümlecik ile ifade eder, biri boş diğeri de dolu ağaç içindir.

incele(bos)

incele(agac(A, B, C)):-

incele(A), incele(B), incele(C).

Aşağıdaki ağaç tarama algoritması aşağıya-doğru-arama olarak bilinir. Çünkü Prolog her dalda mümkün olduğu kadar derinlemesine gider, bu dalın sonuna ulaştığı anda geriye döner ve başka bir dalı incelemeye başlar. (Şekil 6.2).

Şekil 6.2. Şekil 6.1’deki ağaç yapısında Aşağıya-Doğru-Arama metodunun uygulanması.

Prologun yukarıdaki ağacı nasıl tarayacağı yukarıda belirtilmiştir. Aşağıdaki program, ağaç yapısını tarayarak ağacın her elemanını ekranda görüntülenir.

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(“Ayşe”, agac(“Fuat”, bos_dal, bos_dal), agac(“Leyla”, bos_dal, bos_dal)))).

Programı yazıp çalıştırılırsa ekranda şunlar görülür.

Emine

Ali

Hasan

Fatma

Ayşe

Fuat

Leyla

yes

aşağıya-doğru-arama Prolog’un bir veri tabanını tararken kullandığı yönteme çok benzer. Bu tarama esnasında cümlecikler ağaç şeklinde düzenlenir ve her bir dal ayrı ayrı incelenerek sorgu başarısız oluncaya kadar işleme devam edilir.

6.10. Bir Ağaç Oluşturmak

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. Prolog, hesaplama yaparak elde ettiği değerlerden bir ağaç oluşturabilir. Her bir adımda, argümanların eşleştirilmesiyle boş alt dalın içine boş olmayan bir dal yerleştirilir. Basit verileri kullanarak bir hücreli bir ağaç oluşturmak çok basittir.

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

Yukarıdaki satır Prolog için “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” anlamına gelir. Ağaç yapısı oluşturmak da en az bu kadar basittir. Örneğin

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

Prosedürü üç argümandan oluşmuştur. İlk ağacı, ikinci ağacın alt dalı olarak alır ve üçüncü ağacı da sonuç olarak verir. Yapılan tek şey ise, sadece argümanları bire bir eşleştirmektir. Örneğin agac(“Ali”, bos_dal, bos_dal) şeklindeki bir yapıyı agac(“Emine”, bos_dal, bos_dal) yapısının sol alt dalı olarak yerleştirilmek istenirse, yazılması gereken tek şey şu hedefi çalıştırmaktır.

sola_yerlestir(agac(“Ali”, bos_dal, bos_dal), agac(“Emine”, bos_dal, bos_dal), T).

T’nin değeri agac(“Emine”, agac(“Ali”, bos_dal, bos_dal), bos_dal)

olur. Aşağıdaki örnekte bu teknik gösterilmiştir.

DOMAINS

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

PREDICATES

agac_olustur(string,agac_yapisi)

sola_yerlestir(agac_yapisi,agac_yapisi,agac_yapisi)

saga_yerlestir(agac_yapisi, agac_yapisi, agac_yapisi)

basla

CLAUSES

agac_olustur(A,agac(A,bos_dal,bos_dal)).

sola_yerlestir(X,agac(A,_,B),agac(A,X,B)).

saga_yerlestir(X,agac(A,B,_),agac(A,B,X)).

basla:-      

%Tek daldan oluşan ağaçları oluşturalım

agac_olustur(“Hasan”,Ha),

agac_olustur(“Fatma”,Fa),

agac_olustur(“Ali”,Al),

agac_olustur(“Fuat”,Fu),

agac_olustur(“Leyla”,Le),

agac_olustur(“Ayse”,Ay),

agac_olustur(“Emine”,Em),

%dalları birleştirelim

sola_yerlestir(Ha, Al, Al2),

saga_yerlestir(Fa, Al2, Al3),

sola_yerlestir(Fu, Ay, Ay2),

saga_yerlestir(Le, Ay2, Ay3),

sola_yerlestir(Al3, Em, Em2),

saga_yerlestir(Ay3, Em2, Em3),

%sonucu göster

write(Em3,’\n’).

GOAL basla.

Program yazılıp çalıştırılınca ekranda şu sonuç görüntülenir.

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)))

yes

Prolog’da bir değişken herhangi bir değeri aldıktan sonra, artık bu değeri değiştirmenin bir yolu yoktur. Bundan dolayı yukarıdaki örnekte çok sayıda değişken ismi kullanılmıştır. Her yeni değer oluştuğunda, yeni bir değişken tanımlamamız gerekir.

6.11. Binary Arama Ağacı

Şimdiye kadar ağaç yapısı, bir ağaç ve elemanları arasındaki ilişkileri göstermek için kullanıldı. Temel amaç bu olsaydı, bunun yerine cümleciklerle ifade edilen olgular kullanmak mümkün olurdu. Oysa ağaç yapısının başka kullanımları da vardır. Ağaç yapılarını kullanarak veri saklamak ve istenildiğinde bu değerleri bulmak çok kolaydır. Bu maksatla oluşturulan ağaç yapısına arama ağacı adı verilir. Programcı açısından buna liste veya array tipindeki verilere bir alternatif gözüyle bakılabilir. Basit bir ağaç yapısını tararken, öncelikle o an içinde bulunulan hücreye, daha sonra bu hücrenin solu ve sağına, belirli bir değeri ararken, bir ağaç yapısındaki bütün hücrelere bakılması gerekebilir.

İşte binary arama ağacı, herhangi bir hücreye bakarak aranan bir değerin hangi alt dalda bulunacağını tahmin edebilecek şekilde tasarlanır. Bunun için veri parçaları arasında ne tür sıralama olacağının (Örneğin alfabetik veya sayısal sıralama) tanımlanması gerekir. Sol taraftaki alt dalda bulunan veri, o an içinde bulunulan hücredeki veriden önce gelir ve sağ taraftan devam edilir. Aşağıdaki akış şemasını inceleyim.

Şekil 6.3. Binary tarama yapısı

Farklı sırada yerleştirilen aynı isimlerin farklı bir ağaç şeması oluşturur. Ayrıca, şemada 10 isim olmasına rağmen, bunlardan herhangi biri en fazla 5 adımda bulunabilir.

Binary bir tarama yapısında bir hücreye bakarken, geriye kalan hücrelerin yarısını elimine edilir. Bu yüzden tarama çok çabuk ilerler. Bir Binary Tarama Yapısındaki bir maddeyi bulmak için gereken zaman ortalama olarak log2N’dir.

Bir ağaç oluştururken, işe önce boş bir ağaç ile başlanır. Daha sonra diğer parçalar teker teker ilave edilir. Bir madde ilave etmek için gereken prosedür, bir maddeyi aramak için gereken ile tamamen aynıdır.

1.     Eğer içinde bulunulan nokta boş bir ağaç ise, buraya bir madde yerleştir.

2.     Değilse, buraya yerleştirilecek maddeyi, orada saklı olan madde ile karşılaştır. Karşılaştırmanın sonucuna göre, maddeyi sol veya sağ alt dala yerleştir.

Bunun için Prolog’a 3 cümle gerekir. İlk cümle:

yerlestir(Yeni_Madde, bos, agac(Yeni_madde, bos, bos):-!.

Bunu konuşma diline “Yeni_madde’yi bos olan yere yerleştirmenin sonucu agac(Yeni_madde, bos, bos) olur.” Buradaki Cut komutu, cümlenin uygun olması durumunda başka bir cümlenin denenmemesi içindir.

İkinci ve üçüncü cümleler boş yerlere yerleştirmek için kullanılır.

Yerlestir(Yeni_Madde, bos, agac(Eleman, Sol, Sag), agac(Eleman, Yeni_Sol, Sag):- Yeni_Madde<Eleman, !, yerlestir(Eleman, Sol, Yeni_Sol).

Yerlestir(Yeni_Madde, bos, agac(Eleman, Sol, Sag), agac(Eleman, Sol, Yeni_Sag):- yerlestir(Yeni_Madde, Sag, Yeni_Sag).

Eğer Yeni_Madde<Eleman olursa, değer sol alt dala yerleştirilir; aksi takdirde sağ alt dala yerleştirilir.

6.12. Ağaca Bağlı Sıralama

Ağaç yapısı oluşturulduktan sonra, bu yapı içerisindeki bütün maddeleri alfabetik olarak elde etmek çok kolaydır. Kullanılacak algoritma aşağıya-doğru-tarama yönteminin değişik bir şeklidir:

1.     Eğer ağaç boş ise hiçbir şey yapma.

2.     Değilse, sol tarafta olan bütün değerleri, daha sonra o anki elemanı, sonra da sağ taraftaki bütün elemanları al.

Prolog diliyle, aşağıdaki şekilde ifade edilir.

Hepsini_al(bos).

Hepsini_al(agac(Madde, Sol, Sag)):-

Hepsini_al(Sol), isleme_devam_et(Madde), hepsini_al(sag).

Örnek:

Aşağıdaki programda ekrandan yazılan karakterler, daha sonra alfabetik sırayla görüntülenmektedir. Karakterler kendi aralarında büyük veya küçük olmalarına göre de sıralanmaktadır. Programda kullanılan bazı yüklemler daha sonra incelenecektir.

DOMAINS

karakter_dizisi = agac(char, karakter_dizisi, karakter_dizisi); son

PREDICATES

nondeterm basla(karakter_dizisi)

eylem(char, karakter_dizisi, karakter_dizisi)

agac_olustur(karakter_dizisi, karakter_dizisi)

yerlestir(char, karakter_dizisi, karakter_dizisi)

agaci_yaz(karakter_dizisi)

nondeterm tekrar

CLAUSES

basla(Agac):-

tekrar,nl,

write(“***********************”),nl,

write(“Agaci guncelleme  :  1 \n”),

write(“Agaci incelemek   :  2 \n”),

write(“Programi bitirmek :  7 \n”),

write(“***********************”),nl,

write(“Tercihiniz > “),

readchar(X),nl,

eylem(X, Agac, Yeni_agac),

basla(Yeni_agac).

eylem(‘1’,Agac,Yeni_agac):-

write(“Istediginiz karakterleri yaziniz, bitirmek için # karakterini giriniz: “),nl,

agac_olustur(Agac, Yeni_agac).

eylem(‘2’,Agac,Agac):-

agaci_yaz(Agac),

write(“\nDevam etmek için bir tusa basiniz..”),

readchar(_),nl.

eylem(‘7’, _, son):-

exit.

agac_olustur(Agac, Yeni_agac):-

readchar(C),

C<>’#’,!,

write(C, ” “),

yerlestir(C, Agac, Gecici_agac),

agac_olustur(Gecici_agac, Yeni_agac).

agac_olustur(Agac, Agac).

yerlestir(Yeni,son,agac(Yeni,son,son)):-!.

yerlestir(Yeni,agac(Eleman,Sol,Sag),agac(Eleman,Yeni_sol,Sag)):-

Yeni<Eleman,!,

yerlestir(Yeni,Sol,Yeni_sol).

yerlestir(Yeni,agac(Eleman,Sol,Sag),agac(Eleman,Sol,Yeni_sag)):-

yerlestir(Yeni,Sag,Yeni_sag).

agaci_yaz(son).

agaci_yaz(agac(Madde,Sol,Sag)):-

agaci_yaz(Sol),

write(Madde, ” “),

agaci_yaz(Sag).

tekrar.

tekrar:-tekrar.

GOAL write(“Yazilan karakterleri siralama “),nl, basla(son).


7. LİSTELER VE REKÜRSİYON

Çok sayıda eleman içeren nesnelerle çalışmak, yani liste işlemek, Prolog’un güçlü yönlerinden biridir. Daha önce kısaca anlatılan bu konu, burada daha ayrıntılı olarak ele alınacaktır. Listelerin ne oldukları, nasıl tanımlandıkları ve uygulama programlarında nasıl kullanılabilecekleri hakkında bazı örnekler çözülecektir. Liste işleme metoduna rekursiv ve prosedürel yönlerden yaklaşırken, Prolog’un çok önemli yüklemlerinden olan member ve append üzerinde durulacaktır.

Daha sonra verilen dahili bir sorgu için mümkün olan bütün çözümleri bulan ve görüntüleyen findall standart yüklemini incelenecektir.

7.1. Listeler

Bir listenin, çok sayıda nesne içeren bir nesne olduğu bilinmektedir. Prolog’daki bir liste, diğer dillerdeki dizilere(array) karşılık gelir. Listelerin dizilerden en önemli farkı, bir diziyi kullanmadan önce bu dizide kaç tane eleman olacağını önceden belirtmenin gerekmemesidir. Eğer birleştirilecek nesnelerin sayısı önceden biliniyorsa, bunlar tek bir bileşik veri yapısının argümanı haline getirilebilir.

Elemanları a, b ve c olan bir liste

[a, b, c]

şeklinde ifade edilir. Burada a, b ve c birer elemandır ve bu elemanlar bir virgül ile ayrılarak […..] arasında yazılırlar.

Örnekler:

[araba, ev, televizyon]

[“Mahmut AKSOY”, “Sefer KAÇAR”, “Mahmut ÜSTÜNDAĞ”]

7.2.1. Liste Tanımlanması

Liste tanımları programların domains bölümlerinde yapılır. Tamsayılardan oluşan bir liste

Domains

tamsayilar_listesi = integer*

şeklinde tanımlanır. Burada * tamsayilar_listesi argümanının tamsayılardan oluşan bir liste olduğunu gösterir. Liste tanımlarken, listeye verilen ismin Prolog’da hiçbir önemi yoktur. Önemli olan şey * ile tanımlı kelimenin bir listeyi temsil ettiğinin belirtilmesidir.

Bir listenin elemanları herhangi bir şey olabileceği gibi, başka listeler de eleman olarak kullanılabilirler. Dikkat edilmesi gereken şey, bir listedeki elemanların tamamının aynı tipde olması, bu elemanların tipinin de ayrıca tanımlanmasıdır.

Örnek:

Domains

Benim_listem = elemanlarim*

elemanlarim= integer /*real, symbol vs. olabilir.*/

Fakat bir listede bulunan standart tiplerin karışık olarak kullanılması mümkün değildir. Örneğin

benim_listem = elemanlarim*

elemanlarim= integer; real; symbol

tanımlaması yanlıştır. Fakat integer, real ve symbol tiplerinden oluşan bir liste tanımlamak için farklı operatörler kullanılabilir:

benim_listem = elemanlarim*

elemanlarim= tamsayi(integer); reel_sayi(real); karakter(symbol)

7.2.2. Bir Listenin Parçaları: Baş ve Kuyruk

Bir liste iki kısımdan oluşur. Bunlar listenin ilk elemanının oluşturduğu baş ve geriye kalan elemanların oluşturduğu kuyruk kısmıdır. Yani bir listenin baş kısmı daima sadece tek eleman, kuyruk kısmı ise daima ayrı bir listeden ibarettir.

Örnek:

[a, b, c] listesinde a listenin başı; b ve c ise kuyruk kısmıdır.

[a] listesinde listenin başı a olur. [], yani boş bir liste de listenin kuyruk kısmıdır. Boş bir listeyi baş ve kuyruk olarak ayırmak mümkün değildir. Dolayısıyla bir listenin kuyruk kısmının her seferinde ilk elemanı alınırsa, sonuçta boş bir listeye ulaşılır. Bu yüzden listeleri bileşik nesneler gibi ağaç yapısında görmek mümkündür. Örneğin [a, b, c, d] listesine bu işlem aşağıdaki gibi uygulanır.

                              liste

                              /    \

                           a    liste

                                 /    \

                               b    liste

                                     /    \

                                   c    liste

                                         /    \

                                        d     [ ]

Burada [a] ile a birbirinin aynısı değildir. Çünkü a tek başına bir eleman iken [a] tam bir bileşik yapıdadır. Çünkü  [a]

         liste

         /    \

        a     [ ]

şeklinde ifade edilir.

7.2.3. Listelerin İşlenmesi

Prologda bir listenin elemanlarını virgüle ayırmak yerine, baş ve kuyruk kısımlarını daha belirgin olarak ifade etmek için sadece baş ve kuyruk kısımları dikey çizgi ile ‘|’ ayrılır.

Örneğin:

[a, b, c] yerine [a|[b, c]] veya benzer şekilde devam edersek [a|[b|[c]]] biçimi kullanılabilir. Burada [c] listesini de baş ve kuyruk olarak ayırırsak, [a|[b|[c|[]]]] olur.

Tablo 7.2. Listelerin baş ve kuyruk halinde gösterilmeleri

Liste

Baş

Kuyruk

[‘a’, ‘b’, ‘c’]

‘a’

[‘b’, ‘c’]

[ ‘a’ ]

‘a’

[] /* Boş liste*/

[ ]

Tanımsız

Tanımsız

[[1, 2, 3], [2, 3, 4], []]

[1, 2, 3]

[[2, 3, 4], []]