Bilgisayar Programlamaya Giriş

Mehmet Gençer

Bu ders notları yeni başlayanlar için uygulamalı olarak bilgisayar programlamanın ana kavramlarını açıklamayı ve temel becerileri edindirmeyi amaçlıyor. Bunu yaparken Racket isimli bir programlama dilini kullanabilmek için DrRacket isimli bir program gerekiyor. Kendi bilgisayarınıza uygun versiyonunu http://racket-lang.org/download/ sayfasından indirip kurmalısınız. Eğer Linux kullanıyorsanız "sudo aptitude install racket" komutunu çalıştırmanız yeterli olur.

Bu ders notlarının içeriği kısmen "How to Design Programs" kitabından alınmıştır.

1 Bilgisayar Programı Nedir?

Bilgisayar programı diğer makinelere benzer. Bazı girdiler alır ve bir çıktı üretir. Bu makinenin girdi çıktıları semboller, rakamlar gibi 'dijital' şeylerdir. Nasıl diğer makineler vida, çark gibi temel parçaları birleştirerek yapılıyorsa bu makineyi de temel parçalardan yapıyoruz. Makineyi bir kasanın içine yerleştiriyoruz, ve genellikle üstüne de ne olduğunu nasıl kullanıldığını anlatan bir etiket koyuyoruz.

Bu makineleri 'gerçekleştirmesi' için DrRacket gibi programlama sistemleri kullanırız. Programcı makinenin hangi temel parçaları nasıl birleştirerek yapılacağını elindeki programlama sisteminin anlayacağı şekilde yazar, yani 'kodlar'. Programlama sistemi de bu makineyi gerçekleştirir, yani işletir.

Şimdi DrRacket programını çalıştıralım ve basit bir makine yapalım:

; iki ile üçü toplayan bir makine
(+ 2 3)

Bunu üst kısımdaki bölüme yazdıktan sonra 'Run' butonuna basarak çalıştırın, aşağıdaki bölümde makinemizin ürettiği çıktı gözükecektir. Makinemiz çok basit temel bir parçayı kullanıyor: toplama. Bu işlemi ifade eden "+" sembolü ve yanındaki işlem girdilerini aralarına en az bir boşluk koyarak ayırmalısınız. Bu parçalara 'atom'lar diyebiliriz.

Makinemiz aynı diğer makinelerde olduğu gibi bir dış kasanın içine yerleştiriyoruz, bu kasa parantezlerden oluşuyor. Parantezler bir komutun başlama ve birme yerini işaretliyor. Makinenin üstüne de bilgisayarın umursamadığı bir etiket yapıştırdık. Makinemize iki tane de girdi verdik: 2 ve 3 sayıları. Makineyi çalıştırınca aşağıda çıktı olarak 5 sayısını göreceksiniz. Burada yaptığımız programlama sisteminin içinde tanımlı bulunan bir temel parçayı kullanmak. Kullandığımız parça bir 'işlev', ya da diğer adıyla 'fonksiyon'. Parantezin hemen arkasına fonksiyonun ne olduğunu belirttik, onun ardından da arada boşluklar bırakarak fonksiyonun ihtiyaç duyduğu girdileri verdik. Dolayısıyla bir fonksiyonu kullandık, ya da programcıların deyişiyle 'çağırdık'.

Diğer makineler hem basit parçaları hem de hazır makineleri birleştirerek yapılır. Örneğin bir araba yapacaksak tekerlek gibi basit parçaların yanısıra bir de motor kullanırız, ki bu motorun kendisi basit parçalardan -bir başkası tarafından- yapılmış bir makinedir. Arabayı yaparken motora girdi olarak benzin veririz, çıktı olarak bize dönme gücü üretir. Biz de bu çıktıyı başka bir parçaya (tekerleğe) girdi olarak bağlarız. Aynı bunun gibi yaptığımız program makineleri de biribirine bağlıyoruz. Örneğin:

(* (+ 2 3) 4)

Bu makine 2 ile 3'ü toplayıp sonra toplamı 4 ile çarpıyor, çalıştırınca 20 sonucunu görürüz. Bu makineyi kapatan dış kasanın içinde ikinci bir kasa var. Bilgisayar önce içteki makineyi çalıştırıyor, yani toplama fonksiyonunu çağırıyor. İçteki makinenin çıktısı dıştaki makinenin girdisine dönüşüyor. Yani çalıştırmamız sonucunda bilgisayar bize göstermeden şöyle bir dönüştürme yapıyor:

(* 5 4)

Bu prensiple temel parçalardan makineler kurup birleştirebiliriz:

(* 2 (/ (+ 3 4) (+ 5 6)))

Burada görüldüğü üzere bir fonksiyonun girdileri basit değerler, yani atomlar olabildiği gibi, başka bir fonksiyon çağrısı içerene ifadeler de olabilir. Atomik bir değer yerine bir ifade koyarsak bilgisayar önce o ifadenin sonucunu bulur, sonra sanki atomik bir değermiş gibi yerine koyar ve daha dıştaki ifadeyi hesaplamak için yoluna devam eder.

2 Fonksiyon ve değişken tanımlama

Şimdi bir kürenin alanını ve hacmini hesaplayan programlar yapmayı deneyelim. Alanı ve hacimi şu formüllerle hesaplıyoruz: \(alan=4\pi\cdot r^{2}\) ve \(hacim=\frac{4}{3} \pi\cdot r^{3}\). Bu formülleri yarıçapı 2.4 olan bir küre ve daireye uygulayacak makinelerimiz altalta şöyle yazılabilir:

(* 3.14 4 (* 2.4 2.4))
(* 3.14 (/ 4 3) (* 2.4 2.4 2.4)) ;çarpma toplama gibi işlemler ikiden fazla sayı girdisiyle de çalışır!

Makine işini yapıyor, ama önemli bazı dezavantajları var. Öncelikle Pi sayısını iki defa tanımlamak zorunda kaldım, üstelik en kaba değerini kullanarak. Bunun yerine Pi sayısını programlama sistemimin temel bir parçası haline getirebilirim. Bunu (define ...) isimli temel fonksiyondan yararlanarak yapacağız:

(define Pi 3.14)
(* Pi 4 (* 2.4 2.4))
(* Pi  (/ 4 3) (* 2.4 2.4 2.4))

define yani tanımla işlemi iki girdi alıyor: birinci girdi bizim seçtiğimiz bir isimdir, ikincisi ise bir değer ifadesidir. define fonksiyonunu çağırdıktan sonra bilgisayar Pi gördüğü her yerde bizim tanımladığımız karşılığını, yani 3.14 değerini konulacaktır. Bunun en önemli avantajı Pi sayısını biraz daha hassas bir değerle kullanmak istersem sadece tek bir yerde define satırını değiştirmek yetecek:

(define Pi 3.1415)
(* Pi 4 (* 2.4 2.4)) 
(* Pi (/ 4 3) (* 2.4 2.4 2.4)) 

Programımın ciddi bir başka sorunu daha var: sadece yarıçapı 2.4 olan küreler için kullanabiliyoruz. Başka küreler için oturup yeni yeni programlar yazmamız gerekecek. Bu sorunu çözmek için kürenin yarıçapını bir girdi olarak alabilen bir 'fonksiyon tanımı' yapacağız. Yine define kullanıyoruz:

(define Pi 3.1415)
(define (alan r)
    (* Pi 4 (* r r)))
(define (hacim r)
    (* Pi  (/ 4 3) (* r r r)))

Buradaki define işleminin birinci parametresi (alan r), dikkat ederseniz bir fonksiyon çağrısına benziyor: parantez içinde fonksiyon ismi ve bir tane de parametre. define işleminin kalan kısmı ise bu fonksiyonu tanımlıyor: r değeri herneyse onu kullanan, basit parçalar -bizim tanımladığımız Pi dahil- kullanan bir makine ifadesi. Bilgisayar define işleminin ilk parametresi, yani (alan ???) şeklinde bir şey gördüğünde onun yerine bizim tanımladığımız karşılığını koyuverir.

Böylece girdisi/girdileri parametrik olan bir makine tanımladık, aynı + veya * işlemi gibi yeni fonksiyonlar, makineler yarattık ve isimlerini de alan ve hacim koyduk. Ama bu programı çalıştırdığınızda hiçbirşey olmayacaktır, çünkü henüz tanımladığımız fonksiyonu hiç çağırmadık. Şimdi bunu yapalım:

(define Pi 3.1415)
(define (alan r)
    (* Pi 4 (* r r)))
(define (hacim r)
    (* Pi (/ 4 3) (* r r r)))
(alan 100)
(hacim 100)

DrRacket size sonucu aşağıda gösterecektir:

125660
4188666.6
> 

Makine bir kez tanımlandıktan sonra isterseniz DrRacket'in alt kısmında sonuçların gösterildiği yerde makineyi farklı girdilerle çağırabilirsiniz:

> (alan 5)
314.15
> 

Makinenin kalıcı olması için 'Save' butonunu kullanarak bir dosyaya kaydedin. Daha sonra DrRacket programını açtığınızda 'File->Open' menüsünden bu dosyayı geri yükleyebilirsiniz.

2.1 Tasarım ve mühendislik pratiği

Mühendislik ürünü her makine gibi bilgisayar programının da rahat kullanımlı ve güvenilir olması gerekir. Buna başka uzmanların (veya sizin!) geliştirmek veya tamir etmek için bu makineyi önlerine koyduklarında rahatça anlayabilmeleri gereksinimini de eklemeliyiz. Bu pratikleri en baştan edinmekte yarar var.

Bu gereksinimleri büyük ölçüde karşılayan bir tasarım reçetesi örneğini aşağıdaki, bir dikdörgenin alanını hesaplayan programda görelim:

;; Fonksiyon: dikdörtgeninAlanı : sayı sayı-> sayı
;; kenar uzunlukları 'kenar1' ve 'kenar2' olan bir dikdörtgenin
;; alanını çıktı olarak geri döndürür

(define (dikdörtgeninAlanı kenar1 kenar2) 
    (* kenar1 kenar2))

;; Testler: 
(check-expect (dikdörtgeninAlanı 5 6) 36) 

DrRacket programlarında ';' ile başlayan satırlar bilgisayarın aldırış etmediği, insanların okuması için yazılmış satırlardır. Programcılar bunlara yorum (İng. comment) diyor. Commentlerden ilki 'Kontrat' makinenin girdi çıktılarını tarif ediyor. Aynı bilgisayarınızın adaptörü gibi: "120 volt alır, 12 volt üretir" benzeri bir ifade bu. Bu kontrat dikdörtgeninAlanı adlı makinenin girdi olarak iki tane sayı aldığını ve çıktı olarak bir sayı ürettiğini söylüyor. Daha sonra makinenin kullanım amacı sözlü olarak açıklanıyor. Sonra bir örnek verilmiş, ve nihayet makine kodu geliyor. Makine koduna dikkat ederseniz daha önce yaptıklarımız gibi birkaç satıra yayılmıştır, ve ikinci satır birinciye gire daha içerlektir. Buna indentation deniyor, ve bu şekilde programın parçaları gözle biribirinden daha kolay ayırt edilebiliyor. Ayrıca hem fonksiyon adını hem de girdilerin isimlerini anlaşılır ve biraz uzunca tuttum. Fonksiyon adına 'f' kenarlara da 'x' ve 'y' diye parametre isimleri verebilirdim. Bilgisayar için farketmez, ama o zaman programım ne ben ne de başkaları tarafından hiç anlaşılır olmayacaktı.

Tasarım reçetesinin en sonunda testler kısmı var. Burada DrRacket programlama sisteminin içerdiği bir fonksiyorun kullandık. check-expect fonksiyonu ilk değerin (ki fonksiyonumuz çağırılarak elde ediliyor) ikinci değere eşit olup olmadığını kontrol ediyor. Bizim durumumuzda programımız doğru çalışmış ki çalıştırınca şu çıktıyı görüyoruz:

The test passed!
>

2.2 Daha karmaşık tasarımlara doğru

Şöyle bir problemi ele alalım: dış çapı \(d\) iç çapı \(i\) olan bir halkanın alanını hesaplayan bir fonksiyon yazacağız. Halkanın alanını bulmak için dış dairenin alanından iç dairenin alanını çıkartmak gerekiyor. Yani iki kere 'dairenin alanı' hesaplamasını yapacağız. Dairenin alanı için \(\pi\cdot r^2\) formülünden şöyle bir fonksiyonu tek hamlede yazabilirdik:

(define Pi 3.1418)

(define (halkaAlanı dışÇap içÇap)     
    (- (* Pi (* dışÇap dışÇap)) (* Pi (* içÇap içÇap))))

Ama bu hiç okunaklı ve anlaşılır değil. Bu problemi çözmenin en iyi yolu daire alanını hesaplayan bir makine tasarlayıp, halka alanını hesaplayacak makinemizde bundan iki tane kullanmak olur. Yani makinemiz biraz karmaşıklaşıyor. Böyle bir problemi çözerken önce bir çözüm şablonu hazırlayalım (bunu hemen çalıştırmayın çünkü bitmiş bir program değil!):

(define (halkaAlanı dışÇap içÇap)
    (- (daireAlanı dışÇap) (daireAlanı içÇap)))

Gördüğünüz gibi elimizde daireAlanı diye bir fonksiyon olursa halkaAlanı fonksiyonu çok kolay ve 'anlaşılır' oluyor. Şimdi de daireAlanı fonksiyonunu ve gerekli Pi tanımını yazalım:

(define Pi 3.1418)

(define (daireAlanı çap)
    (* Pi (* çap çap)))

Şimdi parçaları birleştirip uygun bir tasarım sunumuna oturtuyoruz ve testleri ekliyoruz:

;; Fonksiyon: halkaAlanı : sayı sayı-> sayı
;; Dış çapı d iç çapı i olan bir halkanın 
;; alanını hesaplar. Bunu yaparken daire alanını
;; hesaplayan yardımcı bir fonksiyon kullanır.

(define Pi 3.1418)
(define (daireAlanı çap)
    (* Pi (* çap çap)))
;; Testler:
(check-expect (daireAlanı 5) (* Pi 25)) 

;; Fonksiyon: halkaAlanı: sayı sayı -> sayı
;; dış ve iç çapı verilen halkanın alanını döndürür
(define (halkaAlanı dışÇap içÇap)
    (- (daireAlanı dışÇap) (daireAlanı içÇap)))

;; Testler: 
(check-expect (halkaAlanı 5 3) (* Pi 16)) 

2.3 Egzersizler

  1. Bankadan aylık yüzde \(f\) faizle alınan \(b\) liralık bir mortgage borcun bir ay sonunda ne kadar faizi olacağını hesaplayan bir fonksiyon yazın.

2.4 Egzersiz Çözümleri

1. ;; Fonksiyon: borçFaizi : sayı sayı-> sayı ;; Bankadan aylık yüzde f faizle alınan b kadar borcun ;; bir ay sonunda ne kadar faizi olacağını hesaplar

    (define (borçFaizi faiz miktar) 
        (* miktar (/ faiz 100)))

    ;; Testler: 
    (check-expect (borçFaizi 5 100) 5) 

2.5 Farklı veri girdi çıktı tipleri

Bilgisayar programı sadece sayıları işlemez. Karşılaşacağınız veri tiplerinden örnek bir tanesi metinlerdir. Metinleri "tırnak içinde" yazıyoruz. DrRacket programlama sisteminde bunun için de temel fonksiyonlar var. Örneğin iki metin parçasını birleştirmek (toplamak?) için:

(string-append "merhaba " "oradaki")

Bu fonksiyon iki tane metin parçasını girdi olarak aldı ve çıktı olarak bir metin parçası üretti. Metinler de tanımların konusu olabilirler. Örneğin aşağıdaki programı çalıştırın:

(define isim "Mehmet")
(string-append "merhaba " "Mehmet")
(format "ismin ~a, isminin uzunluğu ~a harf" isim (string-length isim))

Bir başka veri tipi ise mantıksal değişkenlerdir. Mantıksal cebiri geliştiren bilim insanı George Boole'nin anısına bu tür verilere 'Boolean' veriler deniyor. Bunlar doğru veya yanlış değerini alırlar. Mesela 'a sayısı çift sayı mıdır?' veya 'a sayısı b sayısından büyük müdür?' gibi soruların cevapları bu türden veri tipine sahiptirler. Bunun için de hazır bazı fonksiyonlar var:

(> 3 2)

Yukarıdaki küçük-müdür adını verebileceğimiz fonksiyon ilk girdinin ikinci girdiden küçük olup olmadığı sorusunu cevaplıyor. Bu tür cevapları mantıksal işlemler dediğimiz 've' 'veya' işlemleriyle kombine edebiliyoruz:

(and (> 3 2) (<= 3 3))

Bir örnek olarak şu problemi çözelim: verilen bir \(a\) sayısının yine girdi olarak verilen \(b\) ve \(c\) sayılarının arasında kalıp kalmadığını belirleyen bir fonksiyon yazalım.

(define (aradaMı? a b c)
    (and (>= a b) (<= a c)))
(aradaMı? 5 1 10)
(aradaMı? 11 1 10)

Bunu çalıştırırsanız gördüğünüz gibi fonksiyonu ilk çalıştırmanın çıktısı 'true' yani doğru, ikincinin sonucu ise 'false' yani yanlış.

2.6 Egzersizler

  1. Bankadan aylık yüzde \(f\) faizle alınan \(b\) liralık bir mortgage borcun ayda \(ö\) liralık ödemeyle bitip bitmeyeceğini belirleyin. Borcun bitmesi için ödemenin aylık faizden daha büyük olması gerekir. Bu problemi çözmek için daha önceki egzersizde yaptığınız programı kullanarak iki fonksiyonlu bir çözüm tasarımı yapabilirsiniz.

2.7 Egzersiz Çözümleri

1. ;;Fonksiyon: biterMi : sayı sayı sayı -> mantıksal ;; Bankadan belirli bir aylık yüzde faiz oranıyla alınan belirli miktar borcun ;; aylık verilen ödeme tutarıyla sonlu bir sürede bitip bitmeyeceğini hesaplar

    (define (biterMi faiz miktar aylıkÖdeme)
        (> aylıkÖdeme (borçFaizi faiz miktar)))

    ;; Fonksiyon: borçFaizi : sayı sayı-> sayı
    ;; Bankadan aylık yüzde f faizle alınan b kadar borcun
    ;; bir ay sonunda ne kadar faizi olacağını hesaplar

    (define (borçFaizi faiz miktar) 
        (* miktar (/ faiz 100)))

    ;; Testler: 
    (check-expect (borçFaizi 5 100) 5) 
    (check-expect (biterMi 5 100 6) true)
    (check-expect (biterMi 5 100 4) false)

3 Görsel programlar: görüntü aritmetiği

Görsel problemleri matematiksel olanlara göre çok daha hızlı kavrarız. Bu yüzden DrRacket bu tür problemlerle uğraşmaya uygun bazı özellikler sağlayan 'kitaplıklar' içerir. Bu kitaplıkları kullanmak için programlarınızın ilk satırına:

(require 2htdp/image)

konutunu koymalısınız. Daha sonra temel geometrik şekillerle denemeler yapabilirsiniz:

(require 2htdp/image)
(circle 10 "solid" "red")
(rectangle 30 20 "outline" "blue")
(square 40 "solid" "slateblue")
(empty-scene 100 100)
basit şekiller
basit şekiller
basit şekiller
basit şekiller
basit şekiller
basit şekiller
basit şekiller
basit şekiller

Bu fonksiyonların herbiri verilen girdilere göre bir geometrik şeklin görselini çıktı olarak veriyor.

Bu geometrik şekilleri üstüste bindirebiliriz:

(require 2htdp/image)
(overlay 
    (circle 20 "solid" "red")
    (rectangle 50 50 "solid" "blue"))
basit şekiller
basit şekiller

Böyle bir bindirmede pozisyon da seçebiliriz.

(require 2htdp/image)
(place-image 
    (circle 20 "solid" "green")
    50 70
    (empty-scene 100 100))
basit şekiller
basit şekiller

Veya şekilleri yanyana birleştirebiliriz:

(require 2htdp/image)
(beside 
    (circle 20 "solid" "green")
    (rectangle 20 20 "solid" "blue"))
basit şekiller
basit şekiller

veya altalta:

(require 2htdp/image)
(above 
    (circle 20 "solid" "green")
    (rectangle 20 20 "solid" "blue"))
basit şekiller
basit şekiller

Bu ve benzeri birçok işlem için bkz.: http://docs.racket-lang.org/teachpack/2htdpimage.html

3.1 Örnek problem: dama tahtası

Şimdi görsel bir program yapacağız: bir dama tahtası çizmek istiyoruz. İki renkli bir tahta olacak. Yukarıda öğrendiğimiz işlemlerle çok basit bir deneme yapalım:

(require 2htdp/image)
(above 
    (beside 
        (square 20 "solid" "green")
        (square 20 "solid" "black"))
    (beside 
        (square 20 "solid" "black")
        (square 20 "solid" "green")))
basit şekiller
basit şekiller

Bu programla yeşil-siyah ikiye ikilik bir dama tahtası çizebiliyoruz. Ama hem renklerin hem de büyüklüğün parametrik olması çok daha iyi olur! Bunu ancak bir fonsiyonla yapabiliriz:

(require 2htdp/image)

(define (ikiyeİkiDamaTahtası boy boyama renk1 renk2)
    (above 
        (beside 
            (square boy boyama renk1)
            (square boy boyama renk2))
        (beside 
            (square boy boyama renk2)
            (square boy boyama renk1))))

(ikiyeİkiDamaTahtası 50 "solid" "green" "blue")
(ikiyeİkiDamaTahtası 200 100 "yellow" "red") ;burada boyama için 0-255 arasında bir şeffaflık derecesi seçtik
basit şekiller
basit şekiller
basit şekiller
basit şekiller

Daha da iyisi tahtanın her bir gözünün nasıl çizileceğini de bir fonksiyon olarak tanımladığımızda makinemiz daha da becerikli olur (aşağıdaki program için Programlama dili menüsünden 'Advanced student' seçmeniz gerek):

(require 2htdp/image)

(define (gözÇiz1 boy boyama renk)
    (square boy boyama renk))

(define (gözÇiz2 boy boyama renk)
    (overlay 
        (circle (/ boy 5) boyama "black") 
        (square boy boyama renk)))

(define (ikiyeİkiDamaTahtası boy boyama renk1 renk2 gözÇizici)
    (above 
        (beside 
            (gözÇizici (/ boy 2) boyama renk1)
            (gözÇizici (/ boy 2) boyama renk2))
        (beside 
            (gözÇizici (/ boy 2) boyama renk2)
            (gözÇizici (/ boy 2) boyama renk1))))

(ikiyeİkiDamaTahtası 50 "solid" "green" "blue" gözÇiz1)
(ikiyeİkiDamaTahtası 200 100 "yellow" "red" gözÇiz2)
basit şekiller
basit şekiller
basit şekiller
basit şekiller

Bu programda ilk kez olarak bir fonksiyonu başka bir konksiyona girdi olarak kullandım. ikiyeİkiDamaTahtası fonksiyonuna 'gözÇizici' girdisi olarak bir fonksiyonu veriyorum ve her bir gözü o fonksiyonu çalıştırıp onun çıktı değerini kullanarak yerine koyuyor.

Şimdi tahtayı önce 4x4 sonra 8x8 boyutlarına büyütecek eklemeler yapmak istiyorum:

(require 2htdp/image)

(define (gözÇiz1 boy boyama renk)
    (square boy boyama renk))

(define (gözÇiz2 boy boyama renk)
    (overlay 
        (circle (/ boy 5) boyama "black") 
        (square boy boyama renk)))

(define (ikiyeİkiDamaTahtası boy boyama renk1 renk2 gözÇizici)
    (above 
        (beside 
            (gözÇizici (/ boy 2) boyama renk1)
            (gözÇizici (/ boy 2) boyama renk2))
        (beside 
            (gözÇizici (/ boy 2) boyama renk2)
            (gözÇizici (/ boy 2) boyama renk1))))

(define (dörtle şekil)
    (above 
        (beside şekil şekil)
        (beside şekil şekil)))
(define (sekizle şekil)
    (dörtle (dörtle şekil)))


(ikiyeİkiDamaTahtası 50 "solid" "green" "blue" gözÇiz1)
(ikiyeİkiDamaTahtası 200 100 "yellow" "red" gözÇiz2)
(dörtle (ikiyeİkiDamaTahtası 50 "solid" "green" "blue" gözÇiz1))
(sekizle (ikiyeİkiDamaTahtası 50 "solid" "green" "blue" gözÇiz2))
basit şekiller
basit şekiller
basit şekiller
basit şekiller

3.2 Animasyonlar

DrRacket'in başka bir imkanı da ekranda hareketli görüntüler yaratmaya uygun olması. Bir animasyon yapmak için animasyonun \(i\)'nci karesini doğru olarak çizen bir fonksiyona ihtiyacım var. Mesela yeşil bir topun ekranda yukarıdan aşağıya hareket etmesini istiyorsam bunu:

(require 2htdp/image)
(define (topDüşür kareNo)
    (place-image 
        (circle 5 "solid" "green")
        50 kareNo
        (empty-scene 100 100)))

DrRacket'in animasyon sistemi filmin her karesini çizmek için bizim istediğimiz bir fonksiyonu kare no'sunu girdi olarak sağlayıp çağırır. Bu sistemi devreye sokmak için (animate ..) komutunu kullanmak, ve programımızın başına da animasyon kitaplıklarını devreye sokacak bir satır ilave etmek gerekecek:

(require 2htdp/image)
(require 2htdp/universe)
(define (topDüşür kareNo)
    (place-image 
        (circle 5 "solid" "green")
        50 kareNo
        (empty-scene 100 100)))
(animate topDüşür)

3.3 Egzersizler 2A

  1. Yaptığımız animasyon programını ekran büyüklüğü, arkaplan ve top rengi, top büyüklüğü parametrik olacak şekilde değiştirin. Animasyon sistemi bizim seçtiğimiz fonksiyona kare no'sundan başka parametre vermediği için bunları (define ..) kullanarak değişkenler olarak tanımlamanız gerekecek, aynı Pi sayısında yaptığımız gibi.
  2. İlk egzersizde yaptığınız sistemden yararlanarak top ekranın altına gelince tekrar yukarıdan başlamasını sağlayan bir animasyon yapın.

4 Koşullu işlemler ve özyineleme

Bilgisayar çok hızlı aritmetik işlem yapar. Ancak onu hesap makinesi gibi basit makinelerden ayırdeden özellik yazdığımız programların karar verebilmesidir, belli koşullarda belli işlemleri yapabilmesidir. Şimdi en önemli komutlardan biri olan koşullu işlem komutunu öğreneceğiz.

Koşullu işlemler 'boolean', yani doğru/yanlış değerlere bakılarak yapılır. Bunun için kullanacağımız (cond ...) işlemi programlarımızı oluştururken kullanacağımız en önemli makine parçalarından biri. İşlemin şablonu şu şekilde:

(cond
    (koşul1 sonuç1)
    (koşul2 sonuç2)
    ...
    (else sonuç)) ;üstteki koşulların hiçbiri sağlanmadıysa

Bu işlemi kullanırken koşul yerine koyacağımız şey herneyse doğru/yanlış değerine sahip olmalı.

Örnek bir problem olarak bir sayının mutlak değerinin hesaplanmasını yapalım:

(define (mutlakDeğer sayı)
    (cond
        ((< sayı 0) (- sayı))
        (else sayı)))
(check-expect (mutlakDeğer 5) 5)
(check-expect (mutlakDeğer -5) 5)

4.1 Hataları yakalama

Koşullu işlemlerin en çok ihtiyaç duyulduğu işlerden biri yaptığımız makinenin yanlış girdileri tespit etmesi ve makul bir hata mesajı verebilmesidir. Örneğin dairenin alanını hesaplayacak makinemize daire yarıçapı olarak eksi bir sayı verilirse ne olur? Bu durumda yapılabilecek makul bir işlem yok, o yüzden yapılması gereken şey bunu bir hata olarak ilan etmek:

(define Pi 3.1418)
(define (daireninAlanı yarıçap)
    (cond
        ((< yarıçap 0) (error "yarıçap için eksi değer verilmiş"))
        ((= yarıçap 0) 0)
        (else (* Pi yarıçap yarıçap))))
(check-expect (daireninAlanı 5) (* 25 Pi))
(check-expect (daireninAlanı -5) (error "yarıçap için eksi değer verilmiş"))

Programda daireninAlanı fonksiyonu ilk çağrıldığında sonucu üretiyor, ikincide ise hata veriyor ve bunu kırmızı olarak sonuç penceresinde görürsünüz. (error ..) komutu hata ilan etmek için kullanılıyor.

4.2 Egzersizler

  1. Daha önce bankadan borç alma problemini yapmıştık. Şimdi şöyle bir senaryo düşünün: Banka A ve B şeklinde iki sınır değer tanımlamış. Aldığınız borç A'dan küçükse faiz oranı F1, A'dan büyük-eşit ve B'den küçükse faiz oranı F2, ve B'den büyük-eşitse faiz oranı F3 olarak uygulanıyor. Bu 'akıllı faiz' senaryosunda alınan x miktarda borcun bir ay sonra ne kadar faizi olacağını hesaplayan bir fonksiyon yazın.

4.3 Özyineleme

Daha önce muhtemelen duymadığınız bu garip terim programcının en önemli silahıdır diyebiliriz. Özyineleme bir fonksiyonu inşa ederken o fonksiyonu da alt parçalardan biri gibi kullanma işlemine denir. Bu yöntem bir problemi daha küçük bir problem cinsinden ifade etmekte kullanılır.

Örnek olarak faktoriyel işlemini düşünelim. Örneğin 7 sayısının faktoriyeli şöyle hesaplanıyor:

7! = 7 x 6 x 5 x 4 x 3 x 2 x 1

Genel bir ifadeyle faktoriyel şu şekilde yazılır:

n! = n x (n-1) x ... x 2 x 1

Bu tanımın problemi belirsiz sayıda çarpma işlemi içermesi! Bunu yapacak bir program yazamayız.

Oysa 6! değerini bilseydik örneğimizi şu şekilde yazabilirdik:

7! = 7 x 6!

Bunu genelleştirirsek:

\[n!=n\cdot (n-1)!\]

Bu özyinelemeli bir ifade, çünkü faktoriyel tanımının içinde tanımın kendisi geçiyor. Bunun karşılığında hesaplamadaki belirsizlik (kaç çarpma yapacağını bilememe) ortadan kalkıyor. Ama bu ifade de yeterince iyi değil. Çünkü özyinelemenin sağlıklı bir şekilde kullanılması için yinelemenin bir noktada bitmesi gerekir (yoksa sonsuz döngüye gireriz!). Mesela bu formülü 1!'e veya 0!'e uygulamaya kalkarsak işler karışır. Bu yüzden kapsamlı ve tam bir tanım:

\[n!=\left\{ \begin{array}{cc} \textrm{tanımsız} & \textrm{eğer $n < 0$ ise} \\ 1 & 0 \le n \le 1 \\ n\cdot (n-1)! & \textrm{aksi takdirde} \end{array}\right.\]

Bu tanım faktoriyel fonksiyonunun tanımsız olduğu yeri (negatif sayılar), özyinelemeye gerk olmayacak koşulu (0-1 değerleri) ki bu koşulda cevap hemen biliniyor, ve diğer, özyineleme uygulanacak koşulu belirtiyor. Artık bu programı yazabiliriz:

(define (faktoriyel n)
    (cond
        ((< n 0) (error "eksi sayıların faktoriyeli olmaz"))
        ((and (>= n 0) (<= n 1)) 1)
        (else (* n (faktoriyel (- n 1))))))

(check-expect (faktoriyel 1) 1)
(check-expect (faktoriyel 3) 6)
(check-expect (faktoriyel -1) (error "eksi sayıların faktoriyeli olmaz"))

Özyinelemeli programı inceleyin. Bilgisayar içte kullanılan faktoriyel işlemini yerine koyduğu zaman bizim ilk yazdığımız formüle ulaşmıyor mu?

Özyineleme tekniğinin temeli yapılacak hesabı özyinelemeli bir yaklaşımla ifade edebilmektir. Yoksa bu ifadeye karşılık gelen programın yazımı kolaydır.

4.4 Özyinelemeli şekiller

Özyineleme görsel problemlerde de kullanılabilir. Basit bir örnek olarak içiçe daireler çizme problemini alalım. Bu örnekte en dıştaki \(y\) yarıçapına sahip ve herbiri bir öncekinden yüzde \(k\) oranda küçük yarıçapa sahip daireleri üztüste koyacağız. Yani en dıştaki dairenin yarıçapı \(y_1=y\), onun içindekinin yarıçapı \(y_2=y_1\cdot\frac{100-k}{100}\), vesaire. Artık dairelerin yarıçapı \(l\) değerine ulaştığında da bu işleme son vereceğiz, yoksa sonsuza kadar devam eder. Bir örnek aşağıda:

basit şekiller
basit şekiller

Burada da bir özyineleme var. Eğer şeklimizi \(Ş(y,k,l)\) diye temsil edersek matematiksel formüllerde olduğu gibi şu şekilde yazabiliriz: \[Ş(y,k,l)=\left\{ \begin{array}{cc} \textrm{tanımsız} & \textrm{eğer $y\le 0$ ise} \\ \textrm{$y$ yarıçaplı daire} & \textrm{ eğer $y \le l$ ise} \\ \textrm{$y$ yarıçaplı daire üstünde $Ş(y\cdot\frac{100-k}{100},k,l)$} & \textrm{aksi takdirde} \end{array}\right.\]

Bu tanımın rehberliğinde programı şu şekilde yazabiliriz:

(require 2htdp/image)
(define (içiçeDaireler y k l)
    (cond
    ((< y l) (circle y "outline" "black"))
    (else (overlay 
           (içiçeDaireler (* y (/ (- 100 k) 100)) k l)
           (circle y "outline" "black")))))
(içiçeDaireler 100 15 10)

Biraz daha iyileştirmek için rengi ve koyuluğu da parametrik yapabilirsiniz.

(require 2htdp/image)
(define (renk y) 
    (* 255 (/ y 300)))
(define (içiçeDaireler y k l koyuluk renk)
    (cond
    ((< y l) (circle y (round koyuluk) renk))
    (else (overlay 
           (içiçeDaireler (* y (/ (- 100 k) 100)) k l (* koyuluk (/ (- 100 k) 100)) renk)
           (circle y (round koyuluk) renk)))))
(içiçeDaireler 100 4 10 30 "red")

4.5 Yinelemeli programların çalışma performansı

Şimdi yine klasik bir problem olan Fibonacci hesaplamasını yapacağız. İtalya'nın Pisa kentinden olduğu için Pisa'lı Leonardo, ya da takma adıyla Fibonacci bu hesaplamayı tanımlamış. Bir çiftlikte bir çift tavşan alırsak \(n\) ay sonra ne kadar tavşanımız olur. Hesaplamayı yaparken bazı varsayımlar yapmış: (1)tavşanlar hiç ölmez, (2) erişkin bir çift tavşan her ay bir çift tavşan doğurur, (3) yeni doğan bir çift tavşan iki ay sonra doğuracak erişkinliğe ulaşır. Bu durumda çiftlikte bu ay olacak tavşan sayısını bulmak için geçen ayki sayının üstüne yeni doğanları, yani iki ay önce ne kadar navşan varsa o sayıyı eklemek gerekiyor. Birinci ve ikinci ayda sadece bir çift tavşanımız var. Bu durumda formülümüz şu:

\[f(n)=\left\{ \begin{array}{cc} \textrm{tanımsız} & \textrm{eğer $n\le 0$ ise} \\ 1\le n \le 2 & 1 \\ f(n-1)+f(n-2) & \textrm{aksi takdirde} \end{array}\right.\]

Şimdi bunu hesaplayacak fonksiyonu yazabiliriz:

(define (fibonacci n)
    (cond
        ((<= n 0) (error "eksi sayılar için hesaplanamaz"))
        ((and (>= n 1) (<= n 2)) 1)
        (else (+ (fibonacci (- n 2)) (fibonacci (- n 1))))))
(fibonacci 5)
(fibonacci 12)
(fibonacci 36)

Bu programı çalıştırdığınızda en son sonucun (3 yılın sonudaki sayı) hesaplanmasının ne kadar uzun sürdüğüne dikkatinizi çekerim. Bu program çifte özyineleme yapıyoré Fibonacci fonksiyonunun içinke kendisini iki defa kullandık. Yani fonksiyon hesaplanırken sürekli ikiye çatallanıyor. 3 yıl yani 36 ay için hesaplama yaparken kabaca 36 defa çatallanıyor, yani fonksiyon çağrısını neredeyse \(2^{36}\) kez yapması gerekiyor, ki bu çok büyük bir sayı! Geç cevap vermesinin sebebi bu. Şimdilik bunun çözümüne değinmeyeceğiz. Burada bir hata sözkonusu değil, ancak bu hesabı çok daha hızlı yapmanın yolları var.

4.6 Egzersizler

  1. Newton-Raphson metodunu kullanarak bir sayının küpkökünü bulun.
  2. Euler'in yöntemini kullanarak iki sayının en büyük ortak bölenini bulun.
  3. Pi sayısını hesaplayın
  4. (random k) fonksiyonunu kullanarak ekranda n tane rasgele yer ve yarıçapta daire çizdirin.

5 Koşullu işlem ve fonksiyonlarla görseller ve animasyonlar

Özyineleme ve koşullu işlemler programlarımızı önemli ölçüde zenginleştirir. Bunların kullanımını pekiştirmek için bazı görsel problemlere uygulayacağız.

5.1 Sierpinski fraktali

Özyineleme içeren görsellere genel olarak fraktal adını veriyoruz. Bunlar ilginçtir çünkü hem güzel görseller elde edilebilir, hem de doğadaki ağaçlar, yapraklar gibi birçok formun nasıl geliştiğini anlamamıza yardım eder. Fraktal şekiller basit bir temanın kendini özyinelemesiyle elde edilirler.

Sierpinski fraktalinin teması üç tane üçgen yerleştirmesidir. Bu tema soldaki şekilde görülüyor. Fraktal görüntünün özyinelemesi yapılırken her üçgenin yerine de temanın küçük bir tekrarı yerleştirilir. Ve sonra küçük üçgenlerin yerine de, vs. Tabii özyinelemeyi bir noktada durdurmamız gerekir.

basit şekiller basit şekiller

Problemin çözümünde tek bir fonksiyon ve koşullu işlemler kullanıyoruz. Yinelemeyi istediğimiz noktada sonlandırmak için görsel temayı kaç defa içiçe tekrarlayacağımıza dair bir sayaç kullanacağım. Sayacın sıfıra ulaşması artık özyineleme yapılmayacağının işareti olacak. Fraktalin görsel temasını gerçekleştirmek için ise iki üçgeni beside işlemiyle yanyana ve sonra bir üçgeni de above işlemiyle onun üstüne yerleştiriyorum:

(require 2htdp/image)
(define (sierpinski n)
  (cond 
    ((= n 0) (above 
              (triangle 10 "solid" "black")
              (beside 
               (triangle 10 "solid" "black")
               (triangle 10 "solid" "black"))))
    (else  (above 
              (sierpinski (- n 1))
              (beside 
               (sierpinski (- n 1))
               (sierpinski (- n 1)))))))

(sierpinski 5)

Yukarıdaki programı takip ederseniz makinemiz (sierpinski 5)çağrısıyla çalıştırıldığında n sıfır mıdır koşulu, yani (= n 0) gerçekleşmiyor, o yüzden aksi takdirde yani else koşuluna geçiliyor ve üç tane (sierpinski 4) şeklini fraktalin görsel temasına göre birleştirmeye yelteniyor. Elbette bu üç çağrının herbiri de üçer tane (sierpinski 3) çağrısına yolacacak, ta ki 0'a ulaşana kadar. O noktada artık özyineleme çağrısı yapılmayacak, sadece üçgenler çizilip (triangle komutu ile) fonksiyon çağrısı geri dönecek. Sonuç şu şekilde

basit şekiller
basit şekiller

Çizim üzerinde kontrolümü arttırmak için bir varyasyon daha tasarlıyorum. Programın genel akışı aynı, sadece çizim modu ve rengini parametrik yaptım, ayrıca toplam çizimin büyüklüğünü kenar parametresiyle veriyorum çünkü yukarıdaki versiyonda fraktal derinliği arttıkça çizim de büyüyordu:

(require 2htdp/image)
(define (sierpinski n kenar mod renk)
  (cond 
    ((= n 0) (above 
              (triangle kenar mod renk)
              (beside 
               (triangle kenar mod renk)
               (triangle kenar mod renk))))
    (else  (above 
              (sierpinski (- n 1) (/ kenar 2) mod renk)
              (beside 
               (sierpinski (- n 1) (/ kenar 2) mod renk)
               (sierpinski (- n 1) (/ kenar 2) mod renk))))))

  (sierpinski 1 100 "solid" "black")

5.2 Egzersizler

  1. Menger sponge fraktalini (iki boyutlu olarak) çizdirin.
  2. T-square fraktalini çizdirin.
  3. Koch curve
  4. Hexaflake

5.3 Animasyonlar

Daha önce yaptığımız topun ekranda yukarıdan aşağıya gittiği animasyonu hatırlayalım:

(require 2htdp/image)
(require 2htdp/universe)
(define (topDüşür kareNo)
    (place-image 
        (circle 5 "solid" "green")
        50 kareNo
        (empty-scene 100 100)))
(animate topDüşür)

Bu animasyonda top ekrandan çıkıp gidiyordu. Şimdi topu aşağı yukarı sektirmek istiyorum, ve bunun için koşullu işlemlerden yaralanacağım. kareNo parametresi bana animasyonun kaçıncı karesinde olduğumu söylüyor, her karede top 1 birim daha ilerliyor. Ancak kare numarası ekran boyutunu aşınca geri dönmesi lazım. Bunu genelleştirirsem kare numarası ekran boyutunun tek sayı bir katıyla çift sayı bir katı arasındaysa topun aşağıdan yukarı, tam tersiyse yukarıdan aşağı ilerlemesi lazım. Bu koşulu tespit etmek ve top pozisyonunu hesaplamak için için bölme işlemine ilaveten even? (çift sayı mı?), floor (sayı yuvarlama), ve modulo fonksiyonlarından yararlanacağım. İlk programda topun çizileceği dikey kordinatı `kareNo' olarak vermiştim, şimdi dikey koordinat için koşullu bir değer koyacağım:

...
(cond
    ((even? (floor (/ kareNo 100))) (modulo kareNo 100))
    (else (- 100 (modulo kareNo 100))))
...

Bu koşullu hesaplama dikey pozisyonu topun sekme hareketine uygun olarak belirliyor. İlk programdaki kareNo yerine bunu koyabilirim ama programım pek okunaklı olmayacak. Onun yerine sekme pozisyonunu hesaplayan ikinci bir fonksiyon tanımlayıp kullanacağım. Program karmaşıklaşmaya başladığı için biraz da açıklama koymakta yarar var:

(require 2htdp/image)
(require 2htdp/universe)
;sekme no:sayı limit:sayı -> pozisyon:sayı
;aşağı yukarı harette sekme pozisyonunu döndürür
(define (sekme no limit)
    (cond
         ((even? (floor (/ no limit))) (modulo no limit))
         (else (- limit (modulo no limit)))))
;topSektir: kareNo:sayı boy:sayı -> görüntü
;boy yüksekliğinde bir ekranda seken topun kareNo'ncu karedeki görüntüsünü üretir
(define (topSektir kareNo)
 (place-image 
    (circle 5 "solid" "green")
    50 (sekme kareNo 100)
    (empty-scene 100 100)))
(animate topSektir)

Programım oldukça modüler bir halde. Şimdi istersem yatay hareket te ekleyebilirim! Hareketin anlaşılması için ekran büyüklüğünü biraz değiştiriyorum.

(require 2htdp/image)(require 2htdp/universe)
;sekme no:sayı limit:sayı -> pozisyon:sayı
;aşağı yukarı harette sekme pozisyonunu döndürür
(define (sekme no limit)
    (cond
         ((even? (floor (/ no limit))) (modulo no limit))
         (else (- limit (modulo no limit)))))
;topSektir: kareNo:sayı boy:sayı -> görüntü
;boy yüksekliğinde bir ekranda seken topun kareNo'ncu karedeki görüntüsünü üretir
(define (topSektir kareNo)
 (place-image 
    (circle 5 "solid" "green")
    (sekme kareNo 250) (sekme kareNo 100)
    (empty-scene 250 100)))
(animate topSektir)

5.4 Ara bahis: interaktif programlar

DrRacket animasyonlarının kullanıcının fare veya klavye hareketleriyle etkileşmesini sağlamak mümkündür. Etkileşimli animasyonlar yapmak için daha önce kullandığımız animate komutu yerine biraz daha detaylı bir komut kullanacağız. big-bang komutu animasyonu başlatırken sadece ekranda verilen kare no'lu şekli çizecek fonksiyonu değil, fare tıklaması vb. olayları ele alacak fonksiyonları da tanımlamayı sağlıyor.

(require 2htdp/image)
(require 2htdp/universe)

;her saat tiktakında animasyonun durumunu ilerletir
(define (ilerle kare)
  (+ kare 1))

;animasyonun istenilen anını çizer
(define (çiz kare)
 (place-image 
  (circle 5 "solid" "green")
  50 kare
  (empty-scene 100 100)))

;fare olaylarını yakalar
(define (tıkla kare x y olay)
  (cond
    ((string=? olay "button-down") 0) ;fare tıklaması animasyonu sıfırlıyor!
    (else kare)))

;animasyonu yukarıda tanımlı fonksiyonlarla başlatır
(big-bang 1
          (on-draw çiz)
          (on-tick ilerle)
          (on-mouse tıkla))

Bu programa daha önce yaptığımız top sektirme özelliğini eklemeyi size bırakıyorum

6 Değişebilen veri yapıları: veri listeleri

Gerçekte karşılaşacağımız problemlerde işlememiz gereken veri tek bir sayıdan ibaret değildir. Veriler genellikle sayılar, isimler listesi, hatta tablosu şeklinde olur. Şimdi listelerin işlenmesini ve bunun nasıl verimli bir şekilde yapılacağını inceleyeceğiz.

6.1 Liste oluşturma ve açma

Listeleri oluşturmak için list fonksiyonu kullanılır. Bu fonksiyona istediğiniz kadar parametre verebilirsiniz. Bu parametre değerlerini verdiğiniz sırada tutan bir liste oluşturur:

(list 1 2 3 5 17 4)

Bazı durumlarda boşbir liste de oluşturmak gerekebilir:

(list)

Böylece bildiğimiz veri tipleri arasına bir tane daha eklenmiş oldu: liste. Bunun sonucu olarak (1) Racket dilinde bu veri tipiyle ilgili bazı yardımcı fonksiyonlar var, ve (2) bu veri tipinde değerler bir fonksiyona parametre olarak verilebilir ve hatta bir listenin elemanı olabilirler.

(define myList (list 1 5 2))
(list? myList) ; verilen değer bir liste ise `doğru`değerini döndürür
(list 1 2 mylist "abc") ; karışık veri türlerine sahip değerlerden bir liste oluşturur

Bu şekilde oluşturulan listeleri işlemek için üç önemli fonksiyon kullanacağız. Hatırlarsanız özyineleme bahsinde belirsiz sayıda işlemden kaçınmanın yollarını bulmak zorunda kalmıştık. Liste de belirsiz sayıda eleman içerdiği için listeleri işlerken kesin sayıda, iki parçaya ayırıp işliyoruz: ilk eleman, ve ardından gelen, kalan veya kuyruk diyebileceğimiz liste. Listeyi böyle iki parçaya ayırmak için şu komutları kullanacağız:

(define myList (list 1 5 2))
(first myList) ; ilk değeri, yani 1 döndürür
(rest myList)  ;listenin kalanını, yani (list 5 2) döndürür.

first ve rest dışında üçüncü önemli fonksiyon empty? fonksiyonu. Bu fonksiyon verilen listenin boş olup olmadığını söyler:

(define myList (list 1 5 2))
(empty? myList) ; `yanlış`
(empty? (rest myList));  `yanlış`
(empty? (rest (rest myList))); `yanlış`
(empty? (rest (rest (rest myList)))); `doğru`

6.2 Listeleri özyineleme ile işleme

Listeleri işlerken belirsiz sayıda işlem gerektiren durumlarda hep yaptığımız gibi özyineleme kullanacağız. Ayrıca her özyineleme probleminde olduğu gibi elde etmek istediğimiz sonucu özyinelemeye uygun bir şekilde formüle edeceğiz.

İlk olarak bir listenin uzunluğunu bulma problemini ele alalım. Boş bir listenin uzunluğu sıfırdır. Boş olmayan bir listede ise uzunluğu şu şekilde özyinelemeli tarif edebiliriz: boş değilse uzunluğu 1 artı listenin kalanının/kuyruğunun uzunluğudur.

(define (uzunluk liste)
    (cond
        ((empty? liste) 0)
        (else (+ 1 (uzunluk (rest liste))))))

(check-expect  (uzunluk (list 1 5 2)) 3)

Bazı problemlerde ise boş liste kabul edilemez. Örneğin bir listenin son elemanını bulacak fonksiyon yazmak istersek:

(define (last list)
    (cond
        ((empty? list) (error "hatalı girdi"))
        ((empty? (rest list)) (first list))
        (else (last (rest list)))))
(check-expect (last (list 1 2 3)) 3)

Listeleri işlerken daha sorunlu bir durum örneğin listenin maksimum elemanını bulmak istediğimizde ortaya çıkar:

(define (maxDeğer list)
  (cond
    ((empty? list) (error "geçersiz girdi"))
    ((empty? (rest list)) (first list))
    ((> (first list) (maxDeğer (rest list))) (first list))
    (else (maxDeğer (rest list)))))

(check-expect (maxDeğer (list 1 2 3 5 4)) 5)

Bu problem doğası gereği biraz daha uzun bir fonksiyon gerektirdi. Daha kötüsü ise özyineleme sırasında maxDeğer (rest list) çağrısını iki defa yapmak gerekiyor, ki bu da uzun bir listede maksimum bulma işleminin çok yavaş olmasına sebep olacaktır.

Bu durumu gidermek için ikinci bir fonksiyon tanımlayacağız:

(define (maxİkili x y)
  (cond
    ((>= x y) x)
    (else y)))
(define (maxDeğer2 list)
  (cond
    ((empty? list) (error "geçersiz girdi"))
    ((empty? (rest list)) (first list))
    (else (max (first list) (maxDeğer2 (rest list))))))

(check-expect (maxDeğer2 (list 1 2 3 5 4)) 5)

Bu versiyonda maxDeğer (rest list) çağrısı sadece bir defa yapılıyor. İsterseniz bunun ne kadar fark yarattığını görmek için aşağıdaki programı çalıştırın. Programda rasgele sayılardan oluşan bir test listesi yaratmak için bir fonksiyon koydum. Bu fonksiyonu şu aşamada anlamayabilirsiniz, ona ileride değineceğiz. Sondaki iki satır max bulma fonksiyonunun iki farklı versiyonunu 30 rasgele sayıdan oluşan bir liste için çalıştırıyor, ve çalışma süreleri oldukça farklı:

(define (maxDeğer list)
      (cond
        ((empty? list) (error "geçersiz girdi"))
        ((empty? (rest list)) (first list))
        ((> (first list) (maxDeğer (rest list))) (first list))
        (else (maxDeğer (rest list)))))

(define (maxİkili x y)
  (cond
    ((>= x y) x)
    (else y)))
(define (maxDeğer2 list)
  (cond
    ((empty? list) (error "geçersiz girdi"))
    ((empty? (rest list)) (first list))
    (else (max (first list) (maxDeğer2 (rest list))))))

(define (randomListe n k)
  (cond 
    ((< n 1) (error "geçersiz girdi"))
    ((= n 1) (list (random k)))
    (else (cons (random k) (randomListe (- n 1) k)))))

(maxDeğer (randomListe 30 1000))

(maxDeğer2 (randomListe 30 1000))

6.3 Egzersizler

  1. Bir listenin uzunluğunu bulan bir fonksiyon yazın
  2. Bir listedeki sayıların toplamını bulan bir fonksiyon yazın.
  3. Yukarıdaki iki fonksiyonu kullanarak bir listedeki sayıların ortalamasını bulan bir fonksiyon yazın.

7 Biçimli veri yapıları

Listeler birden fazla, ve belirsiz sayıda veriden oluşan bilgi kümelerini tutmak için kullanılabilirler. Ancak bir grup problem var ki farklı bir veri yapısı gerekitiryor. Örneğin bir adres defterini bilgisayarda tutmak istediğinizi farzedin. Basitleştirilmiş olarak her adresin bir kişinin adı, telefonu, ve e-postasından oluştuğunu varsayalım. Bu üç parça bilgiyi bir listede tutabiliriz:

(list "ali" "02121111111" "ali@gmail.com")

Böylece bir adres defteri de oluşturabiliriz:

(define adresDefterim
  (list
    (list "ali" "02121111111" "ali@gmail.com")
    (list "veli" "02122222222" "veli@gmail.com")))

Ancak bu adresileri kullanmak son derece sorunlu olacaktır. Örneğin Veli'nin e-postasını çekip çıkartmak için:

(first (rest (rest (first (rest adresDefterim)))))

Bu zorluğun sebebi bireylerin adresini oluşturan bilgi parçalarıyla ilgili düzeni programda ifade edemiyor oluşumuz. Racket'taki struct yanı yapı ile ilgili mekanizma belirli bir yapıya sahip olan bu türden verilerin yapısını ifade etmemizi sağlıyor:

(define-struct Adres (isim telefon eposta))

Bu yeni komut Racket'ta birkaç fonksiyonun oluşmasına sebep oluyor:

Biz (define-struct ...) kullandığımızda bu fonksiyonlar otomatik olarak yaratılmaktadır.

7.1 Örnek çalışma: Halka çizme

Halka denilen şekil bir iç ve dış yarıçap bilgisinden oluşur. Buna bir de renk bilgisini ekleyelim. Böyle bir şekli temsil etmek için aşağıdaki veri yapısını kullanabiliriz:

(define-struct Halka (içYarıçap dışYarıçap renk))

(make-Halka 100 150 "green")

Şimdi istersek bu türden bir yapıda temsil edilmiş halkayı çizdirebiliriz (Bu ve sonraki örnekleri çalıştırmadan önce DrRacket programının menüsünden "add teachpack" deyip "image.rkt" isimli paketi etkinleştirdiğinizden emin olun. Aksi takdirde görsel programlarımızın başına eklediğimiz iki satırı bu programların da başına eklemeniz gerekecektir):

(define-struct Halka (içYarıçap dışYarıçap renk))

(define (halkaÇiz halka)
  (overlay
    (circle (Halka-içYarıçap halka) "solid" "white")
    (circle (Halka-dışYarıçap halka) "solid" (Halka-renk halka))))

(halkaÇiz (make-Halka 100 150 "green"))

Gördüğünüz gibi veri yapısını tanımlamak bu iç veri parçasını (iç-dış yarıçaplar ve renk) bir liste olarak vermekten çok daha okunaklı bir program oluşturmamıza yarıyor.

7.2 Listeler ve biçimli verileri beraber kullanma

Yukarıdaki örnekteki tanımı yaptığımız anda Halka Racket için anlamlı bir veri haline geliyor, dolayısıyla bu verileri liste elemanı olarak kullanabiliriz, aynı adres defterinde olduğu gibi.

(define-struct Halka (içYarıçap dışYarıçap renk))

(define (halkaÇiz halka)
  (overlay
    (circle (Halka-içYarıçap halka) "solid" "white")
    (circle (Halka-dışYarıçap halka) "solid" (Halka-renk halka))))

(define (halkalarıYanyanaÇiz halkalar)
  (cond
    ((empty? (rest halkalar)) (halkaÇiz (first halkalar)))
    (else (beside 
           (halkaÇiz (first halkalar)) 
           (halkalarıYanyanaÇiz (rest halkalar))))))

(halkalarıYanyanaÇiz 
 (list
  (make-Halka 100 150 "green")
  (make-Halka 80 100 "red")
  (make-Halka 50 90 "blue")))

Bu programdaki özyinelemede yeni bir durum yoktur. Sadece yapının parçalarını alan komutları kullanıyoruz.

8 Animasyonlarla uygulamalar

İnteraktif programlar bahsinde big-bang komutu ile basit bir animasyon yaratmıştık. O animaasyonda "dünyanın durumu"nu sadece bir tamsayı ile ifade etmiştik. Animasyon yaratırken dünyanın durumunu ifade edecek veriyi oluşturmak ve yorumlamak bizim tasarımımıza kalmıştır. Bu bölümde bu türden tasarımlar yapacağız.

big-bang ile yapılan tasarımda kullandığımız fonksiyonların hepsi "dünyanın şimdiki durumu"nu parametre olarak alırlar. Bunlardan bazıları "dünyanın bir sonraki durumu"nu üretmekle görevlidir. Daha önceki örneğimizde on-tick (saatin lerlemesi) ve on-mouse (mouse tıklaması) olayları için yazdığımız fonksiyonlar bunu yapıyorlardı:

;her saat tiktakında animasyonun durumunu ilerletir
(define (ilerle durum)
  (+ durum 1))

;animasyonun istenilen anını çizer
(define (çiz durum)
 (place-image 
  (circle 5 "solid" "green")
  50 durum ;durum sadece bir sayı ve topun Y koordinatı olarak yorumlanıyor
  (empty-scene 400 400)))

;fare olaylarını yakalar
(define (tıkla durum x y olay)
  (cond
    ((string=? olay "button-down") 0) ;fare tıklaması animasyonu sıfırlıyor!
    (else durum)))

;animasyonu yukarıda tanımlı fonksiyonlarla başlatır
(big-bang 1 ;başlangıç durumu, bir sayıdan ibaret
          (on-draw çiz)
          (on-tick ilerle)
          (on-mouse tıkla))

8.1 Örnek: top hareketi ve sektirme

Bu kez animasyondaki topu belirli bir yeri, hızı, yarıçapı ve rengi olan bir "biçimli veri" olarak temsil edeceğiz. Böylece topu şimdiki yeri ve hızına göre hareket ettirme imkanımız doğacak. İlk yapacağımız versiyonda topun duvarlardan sekmesi durumunu gözardı ediyoruz:

(define-struct Top (x y hızX hızY yarıçap renk))
(define başlangıçDurumu (make-Top 1 1 10 3 30 "blue"))
(define ekranBoyuX 400)
(define ekranBoyuY 400)

(define (ilerle durum)
  (make-Top 
   (+ (Top-x durum) (Top-hızX durum)) (+ (Top-y durum) (Top-hızY durum))
   (Top-hızX durum) (Top-hızY durum)
   (Top-yarıçap durum) (Top-renk durum)))

;animasyonun istenilen anını çizer
(define (çiz durum)
  (place-image 
   (circle (Top-yarıçap durum) "solid" (Top-renk durum))
   (Top-x durum) (Top-y durum)
   (empty-scene ekranBoyuX ekranBoyuY)))

;fare olaylarını yakalar
(define (tıkla durum x y olay)
  (cond
    ((string=? olay "button-down") başlangıçDurumu) ;fare tıklaması animasyonu sıfırlıyor!
    (else durum)))

;animasyonu yukarıda tanımlı fonksiyonlarla başlatır
(big-bang başlangıçDurumu
          (on-draw çiz)
          (on-tick ilerle)
          (on-mouse tıkla))

Bu programa topun x ve y koordinatlarının ekrandan taşma durumunu -basitçe- eklerşek topu sektiren bir animasyon elde edebiliriz. Bunun için sadece ilerle fonksiyonunu değiştireceğim:

(define-struct Top (x y hızX hızY yarıçap renk))
(define başlangıçDurumu (make-Top 1 1 10 3 30 "blue"))
(define ekranBoyuX 400)
(define ekranBoyuY 400)

(define (ilerle durum)
  (cond
    ((or (<= (Top-x durum) 0) (>= (Top-x durum) ekranBoyuX))
     (make-Top 
      (- (Top-x durum) (Top-hızX durum)) (+ (Top-y durum) (Top-hızY durum))
      (- (Top-hızX durum)) (Top-hızY durum)
      (Top-yarıçap durum) (Top-renk durum)))
    ((or (<= (Top-y durum) 0) (>= (Top-y durum) ekranBoyuY))
     (make-Top 
      (+ (Top-x durum) (Top-hızX durum)) (- (Top-y durum) (Top-hızY durum))
      (Top-hızX durum) (- (Top-hızY durum))
      (Top-yarıçap durum) (Top-renk durum)))
    (else
     (make-Top 
      (+ (Top-x durum) (Top-hızX durum)) (+ (Top-y durum) (Top-hızY durum))
      (Top-hızX durum) (Top-hızY durum)
      (Top-yarıçap durum) (Top-renk durum)))))

;animasyonun istenilen anını çizer
(define (çiz durum)
  (place-image 
   (circle (Top-yarıçap durum) "solid" (Top-renk durum))
   (Top-x durum) (Top-y durum)
   (empty-scene ekranBoyuX ekranBoyuY)))

;fare olaylarını yakalar
(define (tıkla durum x y olay)
  (cond
    ((string=? olay "button-down") başlangıçDurumu) ;fare tıklaması animasyonu sıfırlıyor!
    (else durum)))

;animasyonu yukarıda tanımlı fonksiyonlarla başlatır
(big-bang başlangıçDurumu
          (on-draw çiz)
          (on-tick ilerle)
          (on-mouse tıkla))

9 Liste inşası

Şimdiye kadar bir ya da birkaç değer ve/veya listeyi alan ve bir değer üreten fonksiyonlar yazdık. Şimdi ise sonuç olarak bir liste üreten fonksiyonlar yazmaya başlayacağız. Elbette özyineleme kullanmamız gerekecek, ve bu yüzden de listeleri parça parça oluşturmak zorunda kalacağız.

Listeleri parça parça oluşturmak için iki fonksiyondan yararlanıyoruz. Birincisi cons, ki bu bir değer ve bir liste alıyor, listenin önüne değeri ekleyerek yeni bir liste üretip geri döndürüyor.

(cons 1 empty) ; (list 1) döndürür
(cons 1 (list 1 2 3 4)); (list 1 1 2 3 4) döndürür

İkinci fonksiyon ise append. Bu fonksiyon ise listenin sonuna bir ekleme yapıyor. constan farklı olarak her iki parametresi de bir liste olmalı:

(append (list 1 2) (list 3 4)); (list 1 2 3 4) döndürür

9.1 Örnek: n'den 1'e kadar sayılar

Bu örneğimizde öyle bir fonksiyon yazmak istiyoruz ki pozitif bir \(n\) sayısı alsın ve \(n\)'den 1'e kadar sayılardan oluşan bir liste döndürsün:

(define (tersSıralı n)
    (cond
        ((< n 0) (error "hatalı girdi"))
        ((= n 1) (list 1))
        (else (cons n (tersSıralı (- n 1))))))

(check-expect (tersSıralı 3) (list 3 2 1))

Programımız özyineleme ile \((n-1)\) değeri için bir liste oluşturduktan sonra başına \(n\) sayısını ekleyerek işini bitiriyor. Bu yüzden cons komutunu kullandık.

9.2 Örnek: 1'den n'e kadar sayılar

Bu kez aynı işlemi 1'den \(n\)'e kadar olan sayıları döndürecek şekilde yapacağız. Ancak bu kez özyineleme bize 1'den \((n-1)\)'e kadar sayıların listesini döndüreceğinden \(n\) sayısını sona eklememiz gerekecek, bu yüzden de append komutunu tercih edeceğiz:

(define (sıralı n)
    (cond
        ((< n 0) (error "hatalı girdi"))
        ((= n 1) (list 1))
        (else (append (sıralı (- n 1)) (list n)))))

(check-expect (sıralı 3) (list 1 2 3))

9.3 Örnek: Listedeki çift sayıları süzme

Bu kez öyle bir fonksiyon yazmak istiyoruz ki bir sayı listesi alsın ve bize o listedeki çift sayılardan oluşan bir liste döndürsün. Bu kez özyinelemeyi koşullu olarak yapacağız:

(define (çiftSayılarıSeç liste)
    (cond
        ((empty? liste) empty)
        ((even? (first liste)) (cons (first liste) (çiftSayılarıSeç (rest liste))))
        (else (çiftSayılarıSeç (rest liste)))))

(check-expect (çiftSayılarıSeç (list 1 2 3 4 5 6 7)) (list 2 4 6))

9.4 Fonskyionları değer gibi kullanarak daha iyi bir çözüm

Fonksiyonlar da aynı sayı veya listeler gibi bir fonksiyonun parametresi olarak kullanılabilirler. Eğer girdilerden birinin bir fonksiyon olduğunu biliyorsanız o değeri bir fonksiyon çağırır gibi kullanabilirsiniz. Bunu yapmak için DrRacket menüsünden Advanced Student dilini seçmeyi unutmayın.

Örneğin:

(define (uygula fonksiyon değer)
    (fonksiyon değer))

(uygula even? 2)
(uygula odd? 2)
(uygula - 2)

Bu mekanizmayı kullanarak çift sayıları seçme fonksiyonumuzu çok daha genel bir fonksiyon olarak yazabiliriz. Bu seferki fonksiyonumuz bir liste ve bir fonksiyon alacak, bu fonksiyonu listenin her bir elemanına uygulayacak ve sonucu true olan değerleri seçecek:

(define (seç liste seçmeFonksiyonu)
    (cond
        ((empty? liste) empty)
        ((seçmeFonksiyonu (first liste)) (cons (first liste) (seç (rest liste) seçmeFonksiyonu)))
        (else (seç (rest liste) seçmeFonksiyonu))))

(check-expect (seç (list 1 2 3 4 5 6 7) even?) (list 2 4 6))
(check-expect (seç (list 1 2 3 4 5 6 7) odd?) (list 1 3 5 7))

10 Sıralama

Değerleri sıralama bilgisayarda sık ihtiyaç duyulan, ve bu yüzden programlamanın klasiklerinden olan bir problemdir. Liste inşası bahsindeki bilgilerimizle bu problemi çözebileceğiz.

10.1 Yerleştirme ile sıralama

Bu yöntem aynı iskambil oynarken yaptığımız işleme benzer. Karışık gelen kağıtları bir elimizde sıralı olarak biriktiririz. Bu problemi birkaç adımda çözeceğiz.

Adım 1: bir sayıyı sıralı listeye ekleme

İskambil kağıtlarını dizerken yerden kağıdı alıp elimizdeki sıralı kağıtların arasında doğru yere koyma işleminin karşılığıdır. Burada yazacağımız fonksiyon artan sırada sıralanmış bir listeye yeni bir değeri doğru yere yerleştirecek ve bu değeri de içeren sıralı bir liste döndürecek:

(define (sıralıEkle değer sıralıListe)
    (cond
        ((empty? sıralıListe) (list değer))
        ((< değer (first sıralıListe)) (cons değer sıralıListe))
        (else (cons (first sıralıListe) (sıralıEkle değer (rest sıralıListe))))))

(check-expect (sıralıEkle 3 (list 1 2 3 4 5)) (list 1 2 3 3 4 5))
(check-expect (sıralıEkle 3 empty) (list 3))

Adım 2: karışık bir listedeki tüm değerleri sıralı listeye ekleme

Bu adımda problemi neredeyse tamamlıyoruz. Fonksiyonumuza bir sıralı bir de karışık liste verilecek (eldeki sıralı kağıtlar ve yerdeki karışık kağıtlar). Karışık listedeki değerlerin her birini yukarıdaki fonksiyonu kullanarak sıralı listeye ekleyeceğiz:

(define (hepsiniEkle sırasızListe sıralıListe)
    (cond
        ((empty? sırasızListe) sıralıListe)
        (else (hepsiniEkle (rest sırasızListe) (sıralıEkle (first sırasızListe) sıralıListe)))))

(check-expect (hepsiniEkle (list 2 4 3) (list 1 2 3)) (list 1 2 2 3 3 4))

Adım 3: listeyi sıralama

Son bir adımla bu parçaları birleştiriyoruz. Sırasız bir listeyi boş bir listeye sıralı olarak ekleyeceğiz. Gerçekten de iskambilleri dizerken elimiz boş başlıyoruz:

(define (sırala sırasızListe)
  (hepsiniEkle sırasızListe empty))

(check-expect (sırala (list 2 1 4 3)) (list 1 2 3 4))

11 Liste inşası ile animasyon uygulaması

Daha önce yapılandırılmış veri ile tek bir topun güzelce temsil edildiği bir animasyon yapmıştık. Şimdi brunu listelerle birleştirerek çok sayıda topun olduğu bir animasyon yapacağız. Bu animasyonda dünyanın durumu tek bir top değil bir top listesi olarak temsil edilmeli. Dolayısıyla başlangıç durumumu şöyle yapacağım:

 (define başlangıçDurumu (list (make-Top 1 1 10 3 30 "blue") (make-Top 1 1 10 13 20 "green")))

Hatta isterseniz biraz zahmetle de olsa rasgele çok sayıda toptan oluşan bir durum yaratabiliriz. Bunun için random fonksiyonundan yararlanacağım:

(define ekranBoyuX 400)
(define ekranBoyuY 400)

(define (rasgeleToplar n)
    (cond
        ((= n 0) empty)
        (else (cons 
                (make-top (random ekranBoyuX) (random ekranBoyuY) (random 20) (random 20) (random 30) "blue")
                (rasgeleToplar (- n 1))))))
(define başlangıçDurumu (rasgeleToplar 10))

Elbette dünyanın çizimi ve değiştirilmesi de bütün bu listeyi işlemek zorunda. Sonuç programımız aşağıdaki gibi:

(define-struct Top (x y hızX hızY yarıçap renk))
(define ekranBoyuX 400)
(define ekranBoyuY 400)

(define (rasgeleToplar n)
    (cond
        ((= n 0) empty)
        (else (cons 
                (make-Top (random ekranBoyuX) (random ekranBoyuY) (random 20) (random 20) (random 30) "blue")
                (rasgeleToplar (- n 1))))))

(define başlangıçDurumu (rasgeleToplar 10))

(define (ilerle toplar)
  (cond
    ((empty? toplar) empty)
    (else (cons (ilerleTekTop (first toplar)) (ilerle (rest toplar))))))

(define (ilerleTekTop durum)
  (cond
    ((or (<= (Top-x durum) 0) (>= (Top-x durum) ekranBoyuX))
     (make-Top 
      (- (Top-x durum) (Top-hızX durum)) (+ (Top-y durum) (Top-hızY durum))
      (- (Top-hızX durum)) (Top-hızY durum)
      (Top-yarıçap durum) (Top-renk durum)))
    ((or (<= (Top-y durum) 0) (>= (Top-y durum) ekranBoyuY))
     (make-Top 
      (+ (Top-x durum) (Top-hızX durum)) (- (Top-y durum) (Top-hızY durum))
      (Top-hızX durum) (- (Top-hızY durum))
      (Top-yarıçap durum) (Top-renk durum)))
    (else
     (make-Top 
      (+ (Top-x durum) (Top-hızX durum)) (+ (Top-y durum) (Top-hızY durum))
      (Top-hızX durum) (Top-hızY durum)
      (Top-yarıçap durum) (Top-renk durum)))))

;animasyonun istenilen anını çizer
(define (çiz toplar)
  (cond
    ((empty? toplar) (empty-scene ekranBoyuX ekranBoyuY))
    (else (çizTekTop (first toplar) (çiz (rest toplar))))))

(define (çizTekTop durum görüntü)
  (place-image 
   (circle (Top-yarıçap durum) "solid" (Top-renk durum))
   (Top-x durum) (Top-y durum)
   görüntü))

;fare olaylarını yakalar
(define (tıkla durum x y olay)
  (cond
    ((string=? olay "button-down") başlangıçDurumu) ;fare tıklaması animasyonu sıfırlıyor!
    (else durum)))

;animasyonu yukarıda tanımlı fonksiyonlarla başlatır
(big-bang başlangıçDurumu
          (on-draw çiz)
          (on-tick ilerle)
          (on-mouse tıkla))

12 EGZERSİZ ÇÖZÜMLERİ

12.1 1A.1

Bankadan aylık yüzde \(f\) faizle alınan \(b\) liralık bir borcun bir ay sonunda ne kadar olacağını hesaplayan bir fonksiyon yazın.

;; Kontrat: borçFaizi : sayı sayı-> sayı

;; Amaç: Bankadan aylık yüzde f faizle alınan b kadar borcun
;; bir ay sonunda ne kadar faizi olacağını hesaplar


;; Örnek: (borçFaizi 5 100) çağrısı 105 değerini döndürmelidir


;; Program Tanımı: 
(define (borçFaizi faiz miktar) 
    (* miktar (/ faiz 100)))


;; Testler: 
(check-expect (borçFaizi 5 100) 5) 

12.2 2A.1

???