Uygulama Notları: 5

FİZ219 - Bilgisayar Programlama I | 06/11/2020

Döngü ve Karar Cümleleri (I)

Emre S. Tasci emre.tasci@hacettepe.edu.tr

Giriş

Şimdiye kadar basit tanımlar ile, skalerler, vektörler ve matrisleri içeren işlemleri nasıl yapacağımızı inceledik. Fakat bütün bunlar program yazmaktan çok, gelişmiş bir hesap makinesi kullanmaya benziyor. Bilgisayarları daha çok, milyonlarca, milyarlarca tekrarlı işleri bıkıp usanmadan yaptırmak için kullanırız. Einstein'e atfedilen (ama büyük ihtimalle başkasına (Leo Cherne) ait olan) bir sözü alıntılarsak:

Bilgisayarlar inanılmaz hızlı, doğru ve aptallar. İnsanlar ise inanılmaz yavaş, kusurlu ve zekiler. İkisinin birlikteliği her şeyin ötesinde bir güç olur.

(The computer is incredibly fast, accurate, and stupid. Man is incredibly slow, inaccurate, and brilliant. The marriage of the two is a force beyond calculation)

Dosya, dosya, dosya...

Artık hepimiz büyüdük, komutları tek tek yorumlayıcıya girmek yerine, bir dosyaya yazacağız. Bu şekilde, hem geriye dönük düzeltmeleri rahatça yapabileceğiz, hem de ihtiyacımız olduğunda, bulundukları klasörden çağırıp kullanabileceğiz

GNU Octave dosyaları, ".m" uzantılıdır (bunun sebebinin eşleniği olan MATLAB'la uyumluluk olduğunu düşünüyorum -- bu vesileyle, Octave dosyalarınızı MATLAB'da, MATLAB dosyalarınızı da Octave'da sorunsuzca çalıştırabilirsiniz -- sadece çok çok ileri seviyelerde farklılıklar ortaya çıkmakta).

Octave'ı eğer GUI olarak kullanıyorsanız, işler bir nebze kolay:

Açıklamalara başlamadan önce -- eğer sizde menüler/pencereler karmakarışık/istemediğiniz bir halde ise, her zaman için "Window" menüsünün en altında yer alan "Reset Default Window Layout" seçeneği ile varsayılan görünüm düzenine dönebilirsiniz, sıkıntı yok, panik yok ;) Octave_GUI_ResetWindows.png

Octave_GUI.png
Alttaki kulakçıklardan "Editor"ü seçin. Tebrikler, artık buradan dosyalara programlarınızı yazabilirsiniz! 8) Yalnız, "buradan yaz, 'Command Window'dan çalıştır" yaklaşımı kısa sürede yorucu olacağından, size tavsiyem, yazım ve çalıştırma pencerelerini yan yana ayarlamanız. Bunun için aşağıdaki ekran görüntüsündeki okun göstermekte olduğu "Editor" kulakçığına basılı tutup, mouse'un tuşunu bırakmadan hareket ettirin.

Octave_GUI_Editor.png
Biraz hareket ettirince, farklı bölgelerin, farklı doğrultularda gölgelendiğini görürsünüz -- bu, mouse'un tuşunu bırakmanız halinde, editor penceresinin o şekilde yerleştirileceğini size haber verir. Gönlünüze göre bir konum & yönelim bulup, mouse'un tuşunu bırakın:

Octave_GUI_Editor_Yerlesim.png

Yan tarafına da "Command Window"u aktive ederseniz, bu iş tamamdır: Octave_GUI_Editor_Yerlesim2.png

Editördeki kodunuzu birden fazla şekilde çalıştırabilirsiniz:

Eğer GUI değil de, CLI kullanıyorsanız, o zaman sisteminize ve keyfinize bağlı olarak güzel bir editör kullanmanızı tavsiye ederim. Önemli olan, programlarınızı "yalın metin" (plain text) biçiminde ve ".m" uzantısıyla kaydedebilmeniz (Windows kullanıcıları çok dikkatli olsunlar, dosyayı kaydederken tür olarak "Metin Belgesi" seçili olursa, dibine otomatikman ".txt" uzantısı ekler; bu nedenle, "Tüm Dosyalar" türünü seçip, öyle ".m" uzantılı olarak kaydedin. İlgili dersimizin videosunda, arkadaşlarınız sağolsun, onların bilgisayarları üzerinde gerek Windows, gerekse MacOs işletim sisteminde gösterimler var (Linuxçulara zaten tarif gerekmez! ;).

Programlarımızı yazdığımız hale "betik" (script) diyoruz. Arada çok önemli bir fark yok, yani program ile betiği birbirinin yerine kullanırsanız dünyanın sonu gelmez ama bir sinema filmi programsa, senaryosuna betik diyebiliriz. Yani 'program', betiğin çalıştırıldığında ortaya çıkardığı oluşum oluyor aslında...

Bir betiği, dosyasının ismini uzantısız olarak yazarak çalıştırırız. Octave dosyayı kendi çalıştırıldığı aktif yerde ve ön tanımlı birkaç yerde daha arar.

Aktif yeri görmek için pwd komutunu girin:

octave:1> pwd
ans = /home/sururi

İlk betiğimizi yazıp ilk_prog.m adıyla pwd komutuyla gördüğümüz yere kaydedelim. Eğer oraya kaydetmenize izin verilmiyorsa, masaüstüne kaydedin, sonra Octave içinden cd komutuyla masaüstünüzün olduğu yere gidin, örneğin:

octave:2> cd "/home/sururi/Desktop/"
octave:3> pwd
ans = /home/sururi/Desktop

Windows'da dosya yerleri "ters slash" ("\") işareti ile gösterilmesine karşın cd ile yine düz slash'ler ("/") kullanarak belirtin -- en azından dersimizde o şekilde başardık 8) örneğin:
cd "C:/Users/Emre Sururi/Desktop"

Bu aşamadan itibaren artık kodlarınızı bir dosyaya yazıp, o dosyadan çağırabildiğiniz varsayılacaktır. Eğer sıkıntı yaşarsanız, ilgili dersimizin videosundan dikkatlice izleyin, sıkıntı hala devam ederse de tabii ki benimle iletişime geçin, halletmeye çalışalım. 8)

İleri seviye müdahale

Diyelim ki, bilgisayarınızda, tercih ettiğiniz bir yere Octave_Betiklerim gibi bir klasör oluşturdunuz ve buraya koyduğunuz betiklerin Octave tarafından o sırada nerede aktif olursa olsun bulunmasını istiyorsunuz. Bu durumda klasörümüzün bilgisini Octave'a addpath() komutu ile ekleriz (örn., addpath("C:/Klasorumuzun/oldugu/yer/Octave_Betiklerim")). Bu komutu çalıştırdıktan sonra, Octave artık oradaki betikleri de bilecektir (Octave'ın siz bir şey yazdığınızda o isimdeki betikleri aradığı yerler listesini path komutunu girerek görebilirsiniz). Octave'ı her açtığınızda tekrar tekrar bu -veya başka- komutları yazmak istemiyorsanız, kişisel klasörünüze .octaverc adında bir dosya oluşturup, Octave'ın her açıldığında çalıştırılmasını istediğiniz komutları bu dosyaya girersiniz.

İlk ciddi programımız: Kök Bulucu

Artık dosyalara kod yazmayı öğrendiğimize göre, ilk ciddi programımızı yazabiliriz: $a x^2+b x+c=0$ formundaki ikinci dereceden denklemin köklerini bulan "kök bulucu" programı!

İkinci dereceden denklemlerin köklerini nasıl bulacağımızı yaklaşık olarak 2000 yıldan beridir biliyoruz:

$$x_{1,2}=\frac{-b\pm \sqrt{b^2 - 4ac}}{2a}$$

O zaman programımızla, sözgelimi: $3x^2-7x+4=0$ denkleminin köklerini bulalım (genel denklemle karşılaştırdığımızda burada $a=3$, $b=-7$, $c=4$ olduğunu görebiliyoruz... değil mi? 8)

Hazır kökleri bulmuşken, grafiğini de çizdirip, ilgili aralıklarda kontrol edelim, bakalım hakikaten o $x$ değerlerinde x-eksenini kesiyor mu...

Bunları yapan programımızın "kok_bulucu.m" dosyasının içeriği şu şekilde olacak:

# Kök bulucu program.
# Tanımlanan a, b ve c değerleri için
# a*x^2 + b*x + c = 0
# denkleminin kökünü bulur.

# a,b,c değerleri tanımlanıyor
a = 3
b = -7
c = 4

delta = b^2 - 4*a*c

# Hazır formülden kökler hesaplanıyor
x1 = (-b - sqrt(delta)) / (2*a)
x2 = (-b + sqrt(delta)) / (2*a)

# x1 değeri için zoom'lanmış bölge çiziliyor
x = linspace(x1-1E-3,x1+1E-3,100);
y = a*x.**2+b*x+c;

plot(x,y,"b-")
set(gca, "xaxislocation", "origin")
xticks(x1-1E-3:5E-4:x1+1E-3)

# x2 grafiğine geçmeden önce 
# bir tuşa basılana kadar bekletiyoruz.
pause


# x2 değeri için zoom'lanmış bölge çiziliyor
x = linspace(x2-1E-3,x2+1E-3,100);
y = a*x.**2+b*x+c;

plot(x,y,"b-")
set(gca, "xaxislocation", "origin")
xticks(x2-1E-3:5E-4:x2+1E-3)

Ek bilgi: Octave'da n. dereceden polinomların kökünü halihazırda -hem de çok daha etkin bir biçimde- hesaplayan roots fonksiyonu zaten var ama zaten asıl amacımız kökleri bulmak değil de, programlamayı öğrenmek olduğundan oturup açık açık kendimiz yazdık. 8)

For... döngüsü

İlk örnek olarak, 5 kere "Merhaba!" dedirtelim. Bir şeyi en basit şekliyle, yani biçimsiz, süssüz yazdırmak için disp() komutunu kullanırız(*):

Bu örnekteki gibi aynı işlemi birden fazla kez yaptırmak istiyorsak, bir for döngüsünün içine alırız:

Döngüyü tanımlamak için, for diye başlayıp, bir değişkenin alacağı değerleri atarız. Burada i'nin 1'den 5'e olan sayıları, yani [1,2,3,4,5] değerlerini alacağını bildiriyoruz. ("for" denmesinin sebebi: "i'nin 1'den 5'e kadar olan değerleri için şunları yap:" mantığı). Döngünün, yani for bloğunun, sınırlarını da endfor diyerek bildiriyoruz. Octave'da satırların başlarına eklenen boşluklar kaale alınmaz ama blokların nerede başlayıp bittiğini bir bakışta kolayca görmek için bu şekilde girintiler kullanmak epey faydalı bir alışkanlıktır (hatta o kadar faydalıdır ki, Python dilinde mecburi kılınmıştır 8).

5 kere "Merhaba!" demek, tabii ki ne kadar iyi kalpli bir insan olduğumuzu gösterse de, daha faydalı işlere doğru yol açalım. Örneğin, yine 5 kere "Merhaba!" diyelim ama bunu yaparken, merhabalarımızı da sayalım:

Döngü içinde değişken olarak i'yi kullandığımızı ve i'nin değerinin her çevrimde değiştiğine dikkat edin. Sadece i'yi yazdıralım:

Biliyoruz ki 1:5 aralık operatörünü kullanarak, 1'den başlayıp, birer birer arttırarak 5'e kadar sayıyor, yani aslında 1:5 de yazsak, [1 2 3 4 5] de yazsak tamamıyla aynı şeyler:

E madem ki sayı dizisinin değerleri üzerinden gidiyor, karışık sayılar verelim, bakalım o zaman ne olacak:

Beklediğimiz üzere, i, tanımlandığı değerler üzerinden, sırayla, sona ulaşana kadar ilerliyor. Değerler önceden de tanımlanmış olabilir:

Biraz da işe yarar şeyler yapalım, örneğin, 7'den 10'a kadar olan sayıları toplayalım:

Yukarıdaki kodu incelediğimizde, 4 satırın sadece tek bir faktörün değiştirilerek yazıldığını görüyoruz. Demek ki, for döngümüz orayı temsil edecek:

(*) Dipnot: disp() komutunu kullanmasak da, a deyip enter'a basınca a'nın değerini, "Merhaba Dünya!" deyip enter'a basınca da "Merhaba Dünya!" yazdırabiliyoruz -- o halde ne demeye fazladan komut koymuşlar? diye düşünebilirsiniz, en doğal hakkınızdır. disp() kullanımının ikisi kodsal, biri ise yorum açısından faydalı üç kullanımı vardır. Kodsal olarak: bir satırda 3 değişken tanımladığınızı, bunları yaparken de ekrana yazdırmak istediğinizi varsayalım. Birden fazla komut cümlesini ";" işareti ile aynı satıra yazabiliyoruz, örneğin:

ama noktalı virgül aynı zamanda çıktı baskıladığından, a ile b'nin atanmış değerlerini de görmek için disp() kullanabiliriz:

doğrudan değerlerin yazıldığına dikkat edin (aslında aynı satırda farklı komutları "," kullanarak yazınca, sanki enter'a basmış gibi oluyoruz). Bu da bizi ikinci kodsal faydaya getiriyor: disp ile normalde ekrana yazılan bilgiyi bir değişkene de atayabiliriz (a = 5; b = disp(a); gibi).

Ama disp'in asıl faydası, açık şekilde ekrana bir şeyler yazdırdığımızı fark ettirmek (bunu özellikle debugging dediğimiz, programlardan hataları ayıklama işlemlerinde kontrol için sık sık kullanırız), ki bu da yorumsal faydası olmakta.

Zaten asıl kaliteli, biçimlendirilmiş türden ekrana/dosyaya yazdırmak için printf()'i öğrenip rahat edeceğiz -- disp() bu açıdan epey kabaca, iki değişkeni yan yana bile yazdıramıyoruz neticede...

Her iterasyonun sonuçlarını biriktirmek

Fibonacci sayıları, genellikle (0,1) ile başlayan, n. terimin kendisinden önce gelen iki terimin toplamı olarak hesaplandığı ve doğada da şüphe uyandıracak bir sıklıkla karşımıza çıkıp duran bir seridir. Bir program yazıp, 0, 1, 1, 2, 3, 5, 8, ... diye giden bu seriyi hesaplayalım.

Yazmadan önce biraz düşünsek mi ne... ilk başta a = 0, b = 1 diye başladık... c = a + b deyince, c = 0 + 1 = 1 oldu, sonra d = b + c desek, d = 1 + 1 = 2 olur ama bunu bilgisayara tek tek yazmak bitirir bizi... Kafamızı kullanmak lazım...

Her seferinde bulduğumuz terime biz c,d,e,... diye isim verebiliriz ama bunu bilgisayara anlatmak biraz sıkıntılı olduğundan, süreci sistematikleştirmek lazım. Bunu da kullan & at mantığıyla yapalım:

  1. a = 0, b = 1 olarak tanımla.
  2. c = a + b olarak hesapla.
  3. a'ya b'nin; b'ye c'nin değerini ata.
    1. adıma git.

Bu şekilde c'yi her hesaplayışımızdan sonra, a'ya b'nin, b'ye de c'nin değerlerini atadığımızdan, işlemi bir yana kaydırmış oluyoruz ve bu şekilde her zaman için "c = a + b" diyerek, yeni c değerlerini hesaplayabiliyoruz. Bunu koda dönüştürelim:

Burada ilk defa kullandığımız clear komutu, o ana kadar tanımladığımız bütün değişkenleri silip, Octave'ı sanki yeni açmışız gibi yapar.

Gayet güzel sonuç verdi. Peki, ekrana yazdırmak yerine, hesaplanan değerleri bir dizide nasıl saklarız? Bunun için biraz daha düşünelim:

Bu şekilde dizimizi her seferinde yeniden tanımlıyoruz: önce o sıradaki mevcut değerlerini yazıyoruz, sonuna da yeni değerleri. Bu şekilde bakıldığında, pek "ekleme" olmuyor, düpedüz her seferinde diziyi baştan tanımlıyoruz. Ama bir şeye dikkat edin, dizinin değeri örneğin [1 3] iken, buna -2'yi eklemek için [1 3 -2] yazarken, aslında baştaki sayılar dizinin o andaki (yani ekleme yapılmadan önceki) halinin ta kendisi! E o halde, doğrudan o bilgiyi kullanabiliriz:

Bu kavramın bir benzeri kendine etki ettirilen işlemlerde görülür. Örneğin a = 5 olsun, sonra a'ya 2 ekleyelim:

Bu komutu bir daha tekrarladığımızda, a bu sefer 7 + 2 = 9 olacaktır:

Bu davranış, dizimiz için de benzer şekilde geçerli:

Tekrardan Fibonacci dizisi örneğimize dönelim. Sonuçları sonuclar isimli bir dizide toplamak için, öncelikle bu isimde bir dizi tanımlayalım, ilk iki eleman olarak da a ile b elemanlarını belirleyip, sonrasında c'nin değerini her hesapladığımızda, bu diziye ekleyelim:

Çoğu zaman elde ettiğimiz hesap sonuçlarını doğrudan ekrana yazdırmak yerine, sonrasında işleyebilmek adına, bu şekilde dizilerde saklarız.

if kontrol/karar mekanizması

Bilgisayarlara "bu böyle mi? Eğer öyleyse, şöyle yap" dememizin yolu if komutu ile olur. if ile her zaman için cevabı ya "doğru" ("öyledir") ya da "yanlış" ("öyle değildir") olarak verilecek bir önerme sunarız. Yani "5, 3'ten büyük müdür?" ya da "a dizisinin eleman sayısı 6 mı?", "hayatın anlamı 42 mi?" gibi önermeler geçerlidir fakat "hangi sayılar üçten büyük?", "hayatın anlamı nedir/kaçtır?" veya "zzzt ne demek kompodor?" gibi sorular önerme olmadığından geçerli değildir. Geçerli sorularımızı önerme şeklinde vermemiz gerekiyor yani "5, 3'ten büyüktür", "a dizisinin eleman sayısı 6'dır" veya "hayatın anlamı 42'dir" şeklinde.

Eğer belirttiğimiz önerme doğru ise if ile endif arasında tanımlanan kod bloğu çalıştırılır; önerme doğru değilse, ilgili bloktaki kod atlanır, çalıştırılmaz.

Yukarıdaki programımızda 3 adet kontrol işlemi var: a'ya 5 değerini atayıp, kontrollere geçiyoruz. İlkine gelindiğinde önermemiz "a, 5'ten büyüktür" ama a değerimiz 5 olduğundan ve bir sayı kendinden büyük olamayacağından, bu önerme yanlış oluyor ve ilgili bloğun içindeki "a, 5'ten büyüktür" yazdır ifadesi çalıştırılmıyor. Bir sonraki kontrole geldiğimizde bu seferki önermemiz "a, 5'e eşittir" ve hakikaten de öyle olduğundan, bu önerme doğru ve bu nedenle ilgili blok çalıştırılıyor ve "a, 5'e eşit." cümlesi ekrana yazdırılıyor. Buradaki "eşittir" ifadesinin == sembolü ile verildiğine dikkat edin. Şimdiye kadar değer atama yaparken kullandığımız = sembolü "a = 5" gibi bir cümlede kullandığımızda "a'ya 5 değerini ata" anlamına geliyor; halbuki "a == 5" dediğimizde, bu "a, 5'e eşit (mi?)" anlamına gelmekte. Üçüncü önerme de, yani a'nın 5'ten küçük olduğu da doğru olmadığından, onunla ilgili blok da çalıştırılmıyor. (Gelecek ders, bu türden alakalı kontrol ifadelerini tek bir ifadede nasıl toplayabileceğimizi de göreceğiz.)

if bu şekilde tek başına kullanıldığında pek işlevsel görünmese de, for döngüsünün içine yerleştirildiğinde çok etkili bir hale gelir. Bir sonraki örneğimizde verilen aralıkta 6'ya tam bölünebilen sayıları bulduralım. Bir sayının 6'ya bölünüp bölünmediğini kontrol için, modüler aritmetik kullanacağız, yani sayının 6'lık moddaki karşılığının 0 olup olmadığına bakacağız:

mod fonksiyonunun nasıl çalıştığını gördüğümüze göre, işe koşalım:

Örneğimizi bir adım daha ileri götürelim, biz başlangıcı belirtelim, 10 tane bulunca dursun. Bu kontrolü nasıl yaparız? sonuclar dizisinin eleman sayısını kontrol ederek tabii ki! 8)

Nerede biteceğini bilemediğimizden (ille 6'ya bölünen sayılar gibi düşünmeyin, asal sayılar da olabilir, 3'e bölünenler de, karesinin üç fazlası kökünün 5 eksiğinden büyük sayılar da... önemli olan düşünce ve uygulama tarzı), yeterince büyük, sözgelimi $10^15$'e kadar bir aralık kullanalım.

Bir dizinin kaç elemanı olduğunu "number of elements" teriminin kısaltması olan numel fonksiyonu ile de, bulabiliriz, size ve duruma göre rows veya columns komutları ile de, ama en pratiği numeli kullanmak.

break komutu

Yukarıdaki örnekte yeni bir komut kullandık: break. Bu komut, o anda içinde bulunulan döngüyü sonlandırır. Eğer break kullanmasaydık, döngü neredeyse sonsuza kadar sürerdi... bu şekilde gerektiği yerde döngüyü kırıyoruz.

mask_somebody_stop_me.png

Yukarıdaki örnek, dikkat edecek olursanız, çok akıllıca yazılmış bir kod değil. İleride çoklukla tekrar edeceğim üzere if faydalı ama işlemci zamanı açısından pahalı bir komuttur: bilgisayarlar tekrarlı işler için süper uygun, karar verdirme açısından biraz masraflı arkadaşlardır. Peki biz yukarıdaki örnekte ne yapıyoruz?

Bir ara verip, başka bir durumdan bahsedelim: diyelim ki bir arkadaşınızla birlikte hediye paketleri hazırlıyorsunuz. Arkadaşınız hediyeyi alıp kutuya koyuyor, siz de kutuyu alıp, hediye paketi yapıyorsunuz, yapılan iş bu. Üç oda olsun: Arkadaşınız hediyeleri soldaki odada kutulara koyuyor, sonra ortadaki odadaki masanın üzerine bırakıp sizin kapıyı tıklatıp masaya paketlenmek üzere hediyeyi koyduğunu bildirip, bir sonraki hediyeyi kutuya koymak üzere yine kendi odasına çekiliyor. Siz de kendi odanızdan orta odaya geçerek hediyeyi alıyor, kendi odanıza getirip paketliyorsunuz. Arkadaşınız farklı farklı hediyelerle uğraştığından belli bir süre olmuyor, hepsinin zamanı farklı. Bu durumda, en mantıklı olan şey, arkadaşınız "hediyeyi masaya koydum, gelip paketleyebilirsin" anlamında kapınızı çaldığında kalkıp masaya gitmeniz. Bunu durup dururken yapmanız hem zaman kaybı hem de yorgunluk. Böyle anlatınca ne kadar mantıklı geliyor değil mi? Şimdi yukarıdaki kodun sıkıntılı kısmına bir daha göz atalım:

for n = a:1E15
    if (mod(n,6) == 0)
       sonuclar = [sonuclar, n];
    endif
    if (numel(sonuclar)==10)
       break
    endif
endfor

Burada, n 6'ya bölünse de (yani sonuclar dizisinin elemanı bir artsa da), bölünmese de (yani sonuclar dizisinin eleman sayısında bir değişiklik olmasa da), her halikarda gidip "sonuclar dizisinin eleman sayısı 10 oldu mu?" diye sorup duruyoruz ("geldik mi? geldik mi? peki şimdi geldik mi?") -- halbuki mantıklı olan, bu soruyu sadece sonuclar'a ekleme yaptığımız zaman, yani eleman sayısının bir arttığı zaman sormamız olurdu:

Çoklu for döngüleri

İki if bloğu iç içe olur da, iki for bloğu iç içe olmaz mı? İki boyutlu, $(3\times4)$'lük bir matrisin $(i,j).$ hücresinin değerini $i+j$ olarak tanımlamak için satır ve sütunlar üzerinden döngülerimizi iç içe kuralım:

Görüldüğü üzere, ilk olarak, birinci döngüde satir'a 1 değeri atanıyor, ardından ikinci döngü başlayıp, sutun'a 1 değeri atanıyor. İkinci döngü bitene kadar, sutun'a sırası ile, 1, 2, 3 ve 4 değerleri atanıyor. Bu süreçte sutun'un değerine bir mudahale olmadığından, o hep 1.

Sütun döngüsü bitince bu sefer ilk döngünün, yani satır döngüsüne gidiliyor ve satir'ın değeri 2 yapılıyor ve sütun döngüsü başlatılıyor (sutun, yine 1'den 4'e kadar işletiliyor).

Böyle böyle, adım adım, matrisin her bir satırı için sütunlar işlenmiş oluyor.

Ödev

Asal sayı bulucu

1 ile 300 arasındaki asal sayıları bulan program yazınız. (Derste de belirttiğim üzere: burada marifet hazır kod kullanmak değil, for ve if yeteneklerinizi sergilemek. Kodunuzun doğruluğunu primes(300) komutunun sonucuyla kıyaslayarak kontrol edebilirsiniz).

Kök söktürücü

Dersin başında incelediğimiz ve hazır formülle köklerini bulduğumuz $3x^2-7x+4=0$ denklemini tekrardan ele alalım. Kök bulmak için kullanılan metotların biri, kökün, tanım itibarı ile, fonksiyonun 0 değerini aldığı $x$ değeri olduğu gerçeğidir. Eğer fonksiyon kök değerinde 0'a eşitse, bu, fonksiyonun kök değerinde x-eksenini ya yukarıdan aşağıya, ya da aşağıdan yukarıya kesmesi anlamına gelir ("çoğu zaman" ;). O halde belli bir $x_0$ değerinden başlayıp, ufak ufak ilerleyerek şu şekilde (ve istenen hassasiyette) kökü bulacak bir algoritma kurabiliriz:

  1. x0 = 0, adim_boyu = 1E-4 olarak tanımla.
  2. a = 3x0^2-7x0+4 olarak hesaplayıp tanımla.
  3. x1 = x0 + adim_boyu olarak tanımla.
  4. b = 3x1^2-7x1+4 olarak hesaplayıp tanımla.
  5. (a): a ile b'nin işaretleri birbirinden farklı ise aralarında kök var demektir.
    kok = (x0+x1)/2 olarak yazdırıp programı bitir.
    (b): a ile b'nin işareti aynı ise, arasında kök olmadığını varsayıp,
    x0'a x1'in, a'ya b'nin değerini atayıp, 3. adıma git.

Bu algoritmayı anlayıp, program haline getirin.

(Bu vesileyle, bu algoritma işimizi görse de aslında epey hantal bir algoritma; ileride çok daha etkin metotlar göreceğiz kök bulma için)

Bonuslar

  1. Katsayıları verilen herhangi bir ikinci dereceden polinomun köklerini bulsun, bulamıyorsa da bir süre sonra farkına varıp, pes etsin (örneğin $3x^2-7x+4=0$ için aramaya 2'den başlasaydık, sonsuza kadar gidecektik).
  2. Tek (/dejenere) kökü olan 2. dereceden polinomun kökünü bulabilsin (örn. $x^2-4x+4=0$ denkleminin).
  3. Gerçek kökü olmayan polinomun kökünün olmadığını anlasın (/pes etsin) (örn. $x^2-4x+5=0$)

Bonusları çözerken de yukarıdaki algoritmayı geliştireceksiniz: if(b^2-4*a*c == 0) => "tek kök var", if(b^2-4*a*c < 0) => "gerçek kök yok" gibisinden cinlikler yapmaya izin yok. ;)