Algoritmaların C Koduna Dökülmesiyle İlgili Örnekler

Bu yazımızda şu ana kadar gördüğümüz çeşitli deyimler ve dil özelliklerini, algoritmalar ve sözde kodlar cinsinden ifade ettiğimiz problemlerin çözümünde kullanacağız. Bir problemin çözümünü tanımlamak için algoritmaları kullandığımızı söylemiştik. Algoritmaları görsel veya yazılı bir biçimde ifade edebiliyorduk. Yazılı olarak ifade edilen algoritmalar genellikle sözde kod (pseudocode) olarak isimlendirilirken, görsel olarak ifade edilen algoirtmalar genellikle akış şeması (flowchart) adını alıyordu.

Sayaç Kontrollü Yineleme Örneği

Öncelikle ilk problemimizden başlayalım. Bir sınıfta 10 adet öğrenci olsun ve bu 10 öğrencinin her biri bir sınava girmiş olsun. Bu sınavın notları 0 ile 100 arasında olabilsin. Bu sınavın sınıf ortlamasını nasıl hesaplarız?

Önce algoritmik düşünmeye geçmeden düz mantık kuralım. Bir sınıfın not ortalaması demek, aslında bu sınıfta yer alan öğrencilerin notlarının toplamının sınıftaki öğrenci sayısına bölümünden elde edilen sonuç demektir. Bunu yapan algoritma da kısaca kullanıcıdan 10 girdi alıp bu girdilerin toplamını 10 sayısına bölecek ve sonucu ekrana basacaktır.

İlk olarak bu problemin sözde kodunu yazalım. Her bir birim zamanda bir not girmek için burada Sayaç Kontrollü Yineleme (Counter-controlled Iteration) adı verilen bir teknik kullanacağız. Bu teknik Sayaç (Counter) olarak adlandırılan bir değişken kullanarak, döngü deyiminin kaç kez çalıştırılacağına karar vermektedir. Biz burada sayaç değişkeni 10 sayısını aştığında döngüyü sonlandıracağız. Şimdi bu problemin sözde kodunu yazalım:

toplam ← 0;
sayac ← 1;

WHILE (sayac <= 10) 
{
    ogrenciNotu ← INPUT;
    toplam ← toplam + ogrenciNotu;
}

ortalama ← toplam / 10;
PRINT ortalama;

Bu sözde kodun benzerini aslında daha önceki yazılarımda da görmüştük. Sayaç kontrollü yineleme tekniğinde genellikle yineleme sayısı önceden kesin bir biçimde bilinebildiği için, bu tekniğe Kesin Yineleme (Definite Iteration) ismi de verilmektedir. Şimdi hemen bu sözde kodu C diline döküp çıktımıza bakalım:

#include <stdio.h>

int main()
{
    int toplam = 0;
    int sayac = 1;

    while(sayac <= 10)
    {
        int ogrenciNotu;
        printf("Notu girin: ");
        scanf("%d", &ogrenciNotu);

        toplam = toplam + ogrenciNotu;
        sayac = sayac + 1;
    }

    int ortalama = sonuc / 10;
    printf("Sinif ortalamasi: %d\n", ortalama);
    return 0;
}
ÇIKTI
Notu girin: 95
Notu girin: 81
Notu girin: 45
Notu girin: 52
Notu girin: 75
Notu girin: 99
Notu girin: 90
Notu girin: 65
Notu girin: 85
Notu girin: 70
Sinif ortalamasi: 75

Yukarıdaki kodda öncelikle döngüde kullanacağımız sayac değişkeni ve notların toplamını tutacağımız toplam değişkenine başlangıç değerleri atanıyor. Eğer bu değerler atanmazsa, sistem bu değişkenlere çöp değer (garbage value) olarak adlandırılan ve bellekte bu değişkenin ayrıldığı alanda en son tutulan değeri atayacaktır. Bu da yaptığımız işlemlerin hatalı çıkmasına ve mantıksal hatalara yol açacaktır.

while döngüsünde ise döngü koşulundan, döngünün 10 kez döneceğini anlamaktayız. Her bir döngü adımında kullanıcıdan bir değer alınıp ogrenciNotu isimli değişkene atanmaktadır. Daha sonra da girilen değer toplam değişkenine eklenerek, sayac değişkeninin değeri 1 artırılmaktadır.

Döngü dışına çıktığımızda ise elimizde artık girilen 10 not değerinin toplamını tutan bir toplam değişkeni olacaktır. Bu değişkeni öğrenci sayısı olan 10 ile bölerek ortalama değeri buluyoruz. Buradaki bölmenin bir tamsayı bölmesi olduğuna ve virgülden sonraki kısmın dikkate alınmayacağına dikkat edin.

Son olarak bulduğumuz sonucu ekrana basıyoruz. Burada değişkenleri ilk kullanım yerlerinde tanımladığımıza dikkat edin. C’de değişkenleri programın herhangi bir yerinde tanımlayabileceğinizi biliyorsunuz. Fakat değişkenleri bu şekilde tanımlamanın belli avantajları vardır. Değişkenleri bu şekilde ilk kullanım yerlerinden bir öncesinde tanımlamak, onlarla ilgili ortaya çıkabilecek ilklendirme ve gereksiz kullanım sorunlarını büyük ölçüde azaltabilir.

Nöbetçi Değer Kontrollü Yineleme Örneği

Şimdi de aynı problemi 10 adet öğrenci için değil de keyfi miktardaki öğrenci için hesaplayan bir program yazmaya çevirelim. İlk problemde girilen öğrenci sayısı baştan bellidir. Fakat bu problemde kaç tane öğrenci olduğunu belirten bir gösterge olmayacaktır. Peki program nerede not alma işlemini durduracağını nasıl anlayacaktır?

Bu problemi çözmenin bir yolu Nöbetçi Değer (Sentinel Value), Sinyal Değeri (Signal Value), Kukla Değer (Dummy Value) veya Bayrak Değeri (Flag Value) gibi birçok isim alabilen ve girilen değerler serisinin sonunun gelindiğini gösteren bir değer kullanmaktır. Kullanıcı bu değer ile artık veri girişi olmayacağını ve hesaplamanın yapılabileceğini programa bildirebilir.

Fakat bu değer kullanıcının programa girebileceği mantıklı değerler arasından seçilmemelidir. Örneğin bizim sistemimizde not girişi 0 ve 100 sayıları arasından yapılabilir. Örneğin nöbetçi değeri 60 olarak girersek ve bir öğrenci 60 notunu almışsa, kullanıcı not girme esnasında diğer notları giremeden program hesaplama yapma aşamasına geçecektir. Fakat nöbetçi değeri -1 gibi, girilme olasılığı daha düşük bir değer olarak belirlemek daha uygun bir yöntemdir.

Bir nöbetçi değer ile döngü kontrolünü sağlama tekniği Nöbetçi Kontrollü Yineleme (Sentinel-controlled Iteration) olarak adlandırılır. Burada döngünün kaç kez döneceği belli olmadığından, teknik çoğunlukla Kesin Olmayan Yineleme (Indefinite Iteration) olarak da isimlendirilebilir. Burada programı tasarlarken bir teknik daha kullanacağız. Yukarıdan Aşağıya Yaklaşım (Top-down Approach, Stepwise Refinement) olarak adlandırılan bu teknik ile, programları daha genel bir yapıdan daha parçalı bir yapıya indirgeyerek, iyi tanımlanmış program yazma imkanımız olabilmektedir. Şimdi yeni ortalama alma programımız için en yukarıdan başlayarak sözde kodlar yazalım:

Belirsiz sayıda öğrenci içeren bir sınıfın, bir sınavdaki ortalama puanını hesapla

Bu ifade aslında programımızın tamamını yansıtmaktadır. Fakat bize bir C programı yazmak için gerekli olan detaylı bilgileri sağlamamaktadır. Şimdi bu ifadeyi adım adım daha küçük parçalara bölelim. İlk adımda sözde kodumuz şöyle olacaktır:

Değişkenlere başlangıç değeri ata
Sınav notlarını kullanıcıdan girdi olarak alıp topla ve bu notların sayısını say
Sınıfın not ortalamasını hesapla ve ekrana bas

Burada biraz daha detaylı bilgiler görmekteyiz. Ancak yine sınav notlarını nasıl alıp toplayacağımızı ve sayısını nasıl sayacağımızı tam olarak bilmiyoruz. Ayrıca bu program ardışık yapıda ifadeler içermektedir. Fakat kullanıcıdan belirsiz sayıda not değerlerini almak için yinelemeli bir yapıya ihtiyacımız vardır. Bir de baştaki "değişkenler" kelimesinin ne tür değişkenler olduğu da belli değildir. Önce bu kısımı parçalara ayırarak başlayalım. Bu kısım iki ifadeye ayrılabilir:

"toplam" isimli değişkeni 0'a eşitle
"sayac" isimli değişkeni 0'a eşitle

Sadece bu değişkenlere başlangıç değerleri atanması yeterli gibi duruyor. O andaki notu veya ortalama değeri tutacak olan değişkenlere ilk değer vermemize gerek yok. Şimdi sözde kodun ikinci ifadesini parçalara ayıralım:

İlk not değerini kullanıcıdan al (nöbetçi değer olabilir)
Kullanıcı nöbetçi değeri girmediği sürece (while)
    Bu not değerini "toplam" değişkenine ekle
    "sayac" isimli değişkenin değeri 1 artır
    Bir sonraki not değerini kullanıcıdan al (nöbetçi değer olabilir)

Burada while döngüsünün içeriğinde yer alan ifadeleri belirtmek için boşluklar kullandık. Bu sözde kod şu anda C diline dökülebilir bir yapıya geldi. Şimdi de en son sözde kod ifadesini detaylandıralım:

Eğer "sayac" değişkeni 0'a eşit değilse (if)
    "ortalama" değişkenini "toplam / sayac" değerine eşitle
    "ortalama" değişkenini ekrana bas
Eğer "sayac" değişkeni 0'a eşitse (else)
    "Hicbir not degeri girilmedi!" yazısını ekrana bas

Burada yine küçük bir kontrol ekliyoruz. Eğer kullanıcı bir değer girmezse 0 ile bölme gibi bir durumla karşılaşıp programı hata ile sonlandırmamak için bu kontrolü ekliyoruz. Ayrıca bu kontrol ile kullanıcıyı herhangi bir not bilgisi girmediği konusunda uyarıyoruz. Bu küçük uyarılar bile kullanıcının bir programa olan bakışını değiştirir. Çünkü bir programın direk olarak hiçbir bilgi vermeden sonlanması ile ne olduğunu söyleyerek sonlanması arasında fark vardır. Son olarak detaylandırdığımız sözde koda genel bir bakış yapalım:

"toplam" isimli değişkeni 0'a eşitle
"sayac" isimli değişkeni 0'a eşitle
İlk not değerini kullanıcıdan al (nöbetçi değer olabilir)
Kullanıcı nöbetçi değeri girmediği sürece (while)
    Bu not değerini "toplam" değişkenine ekle
    "sayac" isimli değişkenin değeri 1 artır
    Bir sonraki not değerini kullanıcıdan al (nöbetçi değer olabilir)
Eğer "sayac" değişkeni 0'a eşit değilse (if)
    "ortalama" değişkenini "toplam / sayac" değerine eşitle
    "ortalama" değişkenini ekrana bas
Eğer "sayac" değişkeni 0'a eşitse (else)
    "Hicbir not degeri girilmedi!" yazısını ekrana bas

Bu konuşma diline yakın sözde kodu biraz daha programatik bir biçime dönüştürelim:

toplam ← 0;
sayac ← 0;
ogrenciNotu ← INPUT;
WHILE (ogrenciNotu != -1) 
{
    toplam ← toplam + ogrenciNotu;
    sayac = sayac + 1;
    ogrenciNotu ← INPUT;
}
IF (sayac != 0) 
{
    ortalama ← toplam / sayac;
    PRINT ortalama;
}
ELSE 
{
    PRINT "Hicbir not degeri girilmedi!";
}

Bu sözde kod mantıksal olarak 2 adım aşağı ilerlemek ile elde edilmiştir. Birçok programda daha fazla adımda tam algoritmaya ulaşılabilir. Adım adım aşağı inip ifadeleri açma işlemi, ifadelerin hepsi C koduna çevrilebilir bir hale geldiğinde sonlandırılabilir. Böylelikle algoritmalarınızı C koduna (veya başka bir dile) çevirmek oldukça kolaylaşır.

Şimdi C kodunu yazacağız, ancak burada bir şeyi daha belirtmemizde fayda var. Bir önceki programda bulunan ortalama değeri de bir tamsayı değişkeninde tutulduğundan, virgülden sonraki kısmın bir önemi kalmıyordu. Bu kodda bunu da düzelteceğiz. En son ortaya çıkan sözde kodun C kodu karşılığı şöyledir:

#include <stdio.h>

int main()
{
    int toplam = 0;
    int sayac = 0;

    int ogrenciNotu;
    printf("Notu girin (sonlandirmak icin -1 girin): ");
    scanf("%d", &ogrenciNotu);
    while(ogrenciNotu != -1)
    {
        toplam = toplam + ogrenciNotu;
        sayac = sayac + 1;
        printf("Notu girin (sonlandirmak icin -1 girin): ");
        scanf("%d", &ogrenciNotu);
    }

    if(sayac != 0)
    {
        float ortalama = (float)toplam / sayac;
        printf("Sinif ortalamasi: %.2f\n", ortalama);
    }
    else
    {
        printf("Hicbir not degeri girilmedi!\n");
    }

    return 0;
}
ÇIKTI
Notu girin (sonlandirmak icin -1 girin): 95
Notu girin (sonlandirmak icin -1 girin): 81
Notu girin (sonlandirmak icin -1 girin): 45
Notu girin (sonlandirmak icin -1 girin): 52
Notu girin (sonlandirmak icin -1 girin): 75
Notu girin (sonlandirmak icin -1 girin): 99
Notu girin (sonlandirmak icin -1 girin): -1
Sinif ortalamasi: 74.50
ÇIKTI
Notu girin (sonlandirmak icin -1 girin): -1
Hicbir not degeri girilmedi!

Yukarıdaki program iki kez çalıştırılıp farklı senaryolara göre çıktılar alınmıştır. İlk çıktıda 6 adet öğrenci notu girilerek ortalaması alınmıştır. İkinci çıktıda ise herhangi bir not değeri girmeden direk -1 girilince önceden belirlediğimiz yazı ekrana basılmıştır. Nöbetçi değerler kullanırken, bu değerlerin kontrol edildiği değişkenleri döngü içinde değiştirdiğinizden emin olmalısınız. Yoksa sonsuz döngü gibi bazı sorunlarla uğraşır ve bu sorunları bulmak için fazlaca vakit harcarsınız.

ortalama değişkeninin değerini hesaplarken, burada bir açık tür dönüşümü görülmektedir. int türündeki toplam değişkenini yine int türündeki sayac değişkenine böldüğünüzde, bu bir tam sayı bölümü olduğu için virgülden sonraki kısım kesilip (truncate) int türünden bir sonuç ortaya çıkacaktır. Bunu engellemek için öncelikle toplam değişkenini açık bir şekilde float türüne dönüştürüyoruz. Derleyici ilk olarak bu tür dönüşümünü yapacak ve daha sonra bölme işlemini gerçekleştirecektir.

Bölme işleminde ise bir tarafta float, diğer tarafta da int türünden bir değişken olduğu için derleyici kapalı tür dönüşümü ile çıkan sonucu büyük tür olan float türüne dönüştürecektir. Bu sonuç da float türünden olan ortalama değişkenine atanacak ve böylelikle virgülden sonraki kısmı da kaybetmemiş olacağız.

Hemen altındaki satırda ise "%.2f" format belirtecini görmekteyiz. Daha önceki yazılardan hatırlayacağınız gibi bu belirteç ile virgülden sonraki 2 rakamı gösterebiliyorduk. Genellikle sadece "%f" kullandığınızda varsayılan kesinlik değeri 6 olur. Fakat bu durum yine derleyiciden derleyiciye değişebilir.

Son olarak yine bir not geçmekte fayda var. Bildiğiniz gibi String içerisinde "%" ile başlayan format belirteçlerini hem printf gibi ekrana çıktı veren fonksiyonlarda hem de scanf gibi kullanıcıdan formatlı girdi alan fonksiyonlarda kullanmaktayız. scanf gibi fonksiyonlarda format belirteçlerini "%.2f" şeklinde kullanmak yanlıştır. Bunu yapmak size hata geri döndürecektir. Ayrıca virgüllü sayıları C programında doğrudan karşılaştırmak veya bu karşılaştırma üzerine işlemler yapmak da hatalı sonuçlar almanıza neden olabilir. Çünkü bilgisayar dünyasında virgülden sonraki kısım her zaman aynı şekilde gelmez, yaklaşık olarak bir sonuç gelir.

Bu bölümde tek bir problemin 2 yaklaşımı üzerinde durarak, bir problem üzerinde sözde kod yazmayı ve bu sözde kodu çeşitli tekniklerle düzenleyip C koduna dönüştürmeyi gördük. Ayrıca kullanıcıdan veri alma yöntemlerinde çeşitli yaklaşımlar kullanarak, döngü deyimlerinin kontrolünü de anlamış olduk.

5 2 votes
Article Rating
Subscribe
Bildir
guest

2 Yorum
Eskiler
En Yeniler Beğenilenler
onur

if ( sayac != 0 ) olması lazım değil mi acaba