Geçen yazıda dizilerin ne olduğunu, C’de nasıl tanımlandıklarını, ilk değer aldıklarını ve kullanıldıklarını gördük. Bu yazıda dizilerle ilgili biraz daha fazla uygulama yapıp onların kullanımını pekiştirmeyi planlamaktayım. Dizilerin C dilinde oldukça yaygın bir kullanımı vardır. Bu nedenle onların temellerini iyi anlamak oldukça önemlidir. C diliyle ilgili ileride başka konuları da gördüğünüzde muhtemelen diziler bu konular ile birlikte kullanılıyor olacaktır. Bu nedenle C’de dizilerin temel kullanım mekaniklerini anlamak sizin için oldukça faydalı olacaktır. Bu yazıda temel olarak 3 örnekten bahsedeceğim. Örneklerin zor olmayacak ve dizilerle ilgili temel kullanım mekaniklerini anlamnıza yardımcı olacaktır.
Öncelikle çok basit bir örnekle başlayalım. Elimizde İstanbul’un son 30 gününe ait gündüz sıcaklık değerleri bulunsun. Biz de programımızda bu verilerle ilgili bazı istatistikleri (minimum sıcaklık, maksimum sıcaklık, ortalama sıcaklık vb.) çıkaracağız. Bildiğiniz gibi hava tahminleri gibi konularda istatistik çıkarmak oldukça önemlidir ve bu istatistikleri çıkaran uygulamalar da aslında diziler ve onlara benzer veri yapıları kullanılarak tasarlanmış programlardır. Bu nedenle bu çok basit görünen programda bile pek çok yerde kullanabileceğiniz bilgiler bulunur. Şimdi programımızı inceleyelim:
/*
* Code 6.2.1: İstanbulun 30 günlük sıcaklıklarına ait verilerden istatistik çıkarılması.
* Author: Mustafa Yemural
*/
#include <stdio.h>
#include <math.h>
#define DAY_COUNT 30
int main()
{
double temperatures[DAY_COUNT] = {25.0, 24.5, 25.5, 27.0, 27.5, 24.0, 23.5, 24.0, 26.0, 27.0,
27.0, 26.0, 26.0, 25.5, 25.0, 24.0, 21.0, 22.5, 24.0, 25.0,
26.0, 25.5, 25.0, 24.0, 24.0, 24.0, 23.0, 21.5, 22.5, 22.5};
unsigned int i;
/* Find hottest and coldest days: */
double maxTemp = 0;
unsigned int hottestDay = 0;
double minTemp = temperatures[0];
unsigned int coldestDay = 0;
for(i=0; i<DAY_COUNT; ++i) {
if(temperatures[i] > maxTemp) {
maxTemp = temperatures[i];
hottestDay = i;
}
if(temperatures[i] < minTemp) {
minTemp = temperatures[i];
coldestDay = i;
}
}
printf("Hottest day: %d\n", hottestDay + 1);
printf("Max temperature: %.2f\n", maxTemp);
printf("Coldest day: %d\n", coldestDay + 1);
printf("Min temperature: %.2f\n", minTemp);
/* Find average temperature of 30 days: */
double sum = 0;
double average = 0;
for(i=0; i<DAY_COUNT; ++i) {
sum += temperatures[i];
}
average = (sum / DAY_COUNT);
printf("Average temperature: %.2f\n", average);
/* Find standard deviation of the temperatures: */
double squareSum = 0;
double stdDev = 0;
for(i=0; i<DAY_COUNT; ++i) {
squareSum += pow((temperatures[i] - average), 2);
}
stdDev = sqrt(squareSum / DAY_COUNT);
printf("Standard deviation of the temperatures: %.2f\n", stdDev);
return 0;
}
Max temperature: 27.
Coldest day: 17
Min temperature: 21.00
Average temperature: 24.60
Standard deviation of the temperatures: 1.62
Yukarıdaki örnekte sıcaklıklardan istatistik çıkarma kodlarını 3 kısma ayırdım: Minimum ve maksimum sıcaklığı ve bu sıcaklıkların günlerini bulma, ortalama sıcaklığı bulma ve sıcaklıkların standart sapmalarını bulma. İlk olarak 12. satırda sıcaklık verilerini double türünden bir diziye ilk değer olarak verdik. Pratikte bu tür uygulamalarda genellikle verileri kullanıcıdan veya başka kaynaklardan (dosya, web vb.) alırız. Ancak şimdilik kolaylık olması açısından böyle yapıyoruz. Elimizde artık verilerimiz var. 17. ve 38. satırlar arasında bu verilerden maksimum ve minimum sıcaklık değerlerini elde ediyoruz. Bunu tek bir for döngüsü ile yaptık. Buraları uzun uzun anlatmak yerine önemli notları verip geçelim.
Öncelikle dizilerdeki elemanların indislerinin sıfırdan başladığını söylemiştik. Bu nedenle bu 30 günde aslında ilk gün 0. indis, 30. gün ise 29. indis olacaktır. Bu nedenle gün bilgilerini ekrana basarken 1 ile toplayıp basıyoruz. Bir de double türünden verileri ekrana basarken N%.2f kullandığımıza dikkat edin. Böylelikle yalnızca virgülden sonraki 2 basamağı ekrana basıyoruz. Bir başka önemli nokta ise minimum sıcaklığı ve bu sıcaklığın gününü bulmak için tanımladığmız minTemp ve coldestDay değişkenlerine verdiğimiz ilk değerlerdir. Gördüğünüz gibi bu değişkenlere sırasıyla dizinin ilk elemanını ve 0 değerini (yani ilk indisi veriyoruz). Bunun sebebi karşılaştırma yaparken verilerde nasıl bir ilişki olduğunu tam olarak bilmememizden kaynakladır. Yani örneğin oraya 25 yazarsak ve minimum değer 26 ise, for döngüsü içinde bu değeri asla elde edemeyiz. Buna dikkat etmeniz gerekir.
İkinci kısımda ise verilerin ortalamasını buluyoruz. Bu kısımda açıklanacak çok bir şey olacağını düşünmüyorum. Yalnızca toplamı gün sayısına böldüğünüz kısımda elemanlardan biri double ve diğeri int olduğundan sonucun double olarak hesaplanacağına dikkat edin. Böylelikle veri kaybının önüne geçmiş oluruz. Değerlerin standart sapmasını aldığımız kısma gelirsek, önce standart sapmanın nasıl bir formüle sahip olduğunu bilmemiz gerekir. Standart sapma her bir değerden ortalama değeri çıkarıp bunların karelerini alıp toplayıp eleman sayısına bölme ve çıkan sonucun karekökünü almaya dayanır. Yani burada daha önce gördüğümüz ve math.h başlık dosyasında yer alan pow ve sqrt fonksiyonlarını kullanmamızın sebebi budur. Standart sapma değeri ile birçok farklı şeyi hesaplamamız mümkündür. Örneğin ortalama değer ile bu değeri toplayıp çıkartarak normal değer aralığını ve anormal sıcaklıkları saptayabilirsiniz. Gerisini sizin hayal gücünüze ve istatistik bilginize bırakıyorum…
Isınmak için güzel bir örnek oldu. Peki dizileri başka nerelerde kullanabiliriz? Şu ana kadar gördüğümüz konular üzerinden gidecek olursak, dizilere rastgele sayıların kullanıldığı uygulamalarda da sık rastlanmaktadır. Örneğin 100 bin zar atan bir uygulamamız olsun. Bu uygulamayı her çalıştırdığınızda 6 yüzlü bir zarı 100 bin defa fırlattığını ve zardaki hangi numaranın kaç kere geldiği bilgisini tuttuğunu düşünelim. Daha sonra bu 100 bin atışın yüzde kaçının hangi numara gelidiği bilgisini ekrana basalım. Bu uygulamayı temel olarak şu şekilde yazabiliriz:
/*
* Code 6.2.2: 100 bin zar atşından gelen sayıların yüzdesini bulma.
* Author: Mustafa Yemural
*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define FACE_COUNT 6
#define ROLL_COUNT 100000
int main()
{
srand(time(0));
unsigned long diceCount[FACE_COUNT] = {0};
int i;
for(i=0; i<ROLL_COUNT; ++i) {
int current = rand() % 6;
diceCount[current]++;
}
for(i=0; i<FACE_COUNT; ++i) {
double currentPercent = (((double)diceCount[i]) / ROLL_COUNT) * 100;
printf("For %d: %.2f\n", (i + 1), currentPercent);
}
return 0;
}
For 2: 16.68
For 3: 16.56
For 4: 16.62
For 5: 16.71
For 6: 16.55
Örneğimizde öncelikle FACE_COUNT ve ROLL_COUNT isimli sabitlerimizi tanımlıyoruz. Bu sabitler bize program boyunca zarın yüz sayısını ve atış sayısını kullanma konusunda kolaylıklar sağlayacaktır. Daha sonra 15. satırda srand fonksiyonuna o anki zaman değerini vererek, programı her çalıştırdığımızda farklı rastgele sayı serisinin elde edilmesini sağlıyoruz. Zar yüzlerinin gelme sayısını her bir yüz için tutmak amacıyla diceCount isimli bir dizi tanımlıyoruz. Bu dizinin boyutunun unsigned long türünden olması, olası taşmaların (overflow) önüne geçecektir. Ayrıca atış sayısını arttırsak bile, bu türü kullanarak bir yere kadar bu sayıların tutulmasını sağlayabiliriz. Burada bir de sadece ilk elemana 0 değerini atayarak diğer elemanlara tek tek değer vermeden nasıl ilk değer verebileceğimizi de görmektesiniz.
20. satırda yer alan ilk for döngüsü 100 bin kez dönerek, her dönüşte 0 ile 6 arasında (6 dahil değil) rastgele bir sayı elde etmek için kullanılmıştır. Bu döngüde yer alan current değişkeninin alabileceği değerler zaten 0 ile 6 arasında olacağı için dizide bir taşma yaratması söz konusu olmaz. Bu nedenle bu değişkeni dizi elemanlarına erişmek için doğrudan kullanabiliriz. 22. satırda ise dizi elemanlarına son ek olan “++” operatörünün nasıl uygulandığını görüyoruz. Ön ek ve son ek olan “++” ve “—” operatörleri tıpkı değişkenlere uygulandığı gibi dizi elemanlarına da bu şekilde uygulanabilirler. Ayrıca tıpkı normal değişkenlerde olduğu gibi, bu operatörler ile elemanlara uygulanan değişiklikler de kalıcı olmaktadır.
İkinci for döngüsünde ise dizi elemanlarının ekrana basılması gösterilmiştir. Burada diceCount dizisinin elemanları ve ROLL_COUNT sabiti tamsayı türünden olduğundan, bölme işleminde veri kaybı söz konusu olacaktır. Bu nedenle bölme işleminin operandlarından birini ondalıklı sayı türüne çevirmemiz gerekir. Ben burda dizi elemanlarına uygulanan tür dönüşümü işleminin nasıl olacağını göstermek amacıyla diceCount elemanlarını double türüne dönüştürmeyi tercih ettim. Gördüğünüz gibi bunun normal bir değişkenden hiçbir farkı bulunmamaktadır. Yine 27. satırda zarın hangi yüzü olduğu bilgisini ekrana basarken indis numarasına 1 ekleyip bunu yapıyoruz. Çünkü dizi indisleri 0’dan başlamakta, ancak zar yüzleri 1’den başlamaktadır.
Şimdi de kullanıcıdan değer alınması ve dizilere atanması ile ilgili bir örnek yapıp yazımızı sonlandıralım. Bu örnekte kullanıcıdan çeşitli karakter verileri alıp bu karakterlerin sesli ve sessiz harf yüzdesini ekrana bastıracağız. Kabul ettiğimiz karakterler sadece İngiliz alfabesinde yer alan karakterlerin küçük halleri olacak. Bu işi yapmak için uzun switch-case ifadeleri yapmak zorunda kalıyorduk. Ancak burada biraz daha farklı bir yol deneyeceğiz. Öncelikle her bir harfin sayısını 26 elemanlı bir dizide sıralı olarak tutacağız. Burada 0. indis “a” harfine, 25. indis ise “z” harfine denk gelecek. Daha sonra da önceden belirlediğimiz sesli harf indislerini kullanarak sesli harflerin sayısını bulacağız. Son olarak da toplam harf sayısını bulup oranlamayı yapacağız. Şimdi örneğimize bakalım:
/*
* Code 6.2.3: Girilen harflerden sessiz harf ve sesli harf oranını bulma.
* Author: Mustafa Yemural
*/
#include <stdio.h>
#include <ctype.h>
#define ALPHABET_COUNT 26
#define VOWEL_COUNT 5
int main()
{
unsigned int alphabet[ALPHABET_COUNT] = {0};
unsigned int vowelIndexes[VOWEL_COUNT] = {0, 4, 8, 14, 20};
char currentChar;
/* Kullanıcıdan karakter alma ve dizi elemanlarının sayısını arttırma: */
do{
printf("Please enter character: ");
scanf(" %c", ¤tChar);
if(islower(currentChar)) {
alphabet[currentChar - 'a']++;
}
} while(currentChar != '.');
/* Sesli harflerin sayısını bulma: */
unsigned int vowelCount = 0;
int i;
for(i=0; i<VOWEL_COUNT; i++) {
vowelCount += alphabet[vowelIndexes[i]];
}
/* Toplam harf sayısını bulma ve oranları ekrana basma: */
unsigned int totalCount = 0;
int j;
for(j=0; j<ALPHABET_COUNT; j++) {
totalCount += alphabet[j];
}
double vowelRatio = (vowelCount / (double)totalCount) * 100;
double consonantRatio = ((totalCount - vowelCount) / (double)totalCount) * 100;
printf("Vowel Ratio: %.2f%%\n", vowelRatio);
printf("Consonant Ratio: %.2f%%\n", consonantRatio);
return 0;
}
Please enter character: b
Please enter character: v
Please enter character: f
Please enter character: g
Please enter character: u
Please enter character: u
Please enter character: g
Please enter character: r
Please enter character: e
Please enter character: k
Please enter character: l
Please enter character: i
Please enter character: e
Please enter character: q
Please enter character: w
Please enter character: a
Please enter character: c
Please enter character: n
Please enter character: a
Please enter character: .
Vowel Ratio: 40.00%
Consonant Ratio: 60.00%
Şimdi bu kodda göze çarpan kısımları açıklayalım. Öncelikle kullanıcıdan girdi alıp alphabet dizisinin elemanlarının değerlerini arttırmak için bir do-while döngü yapısı görüyoruz. Burada dikkat etmeniz gereken ilk şey scanf fonksiyonunun kullanımıdır. Bu fonksiyon ard arda çağrıldığında, bir sonraki çağrı bir öncekinden kalan boşluk karakterini girdi olarak alacaktır. Bunu çözmek için format String’inin başına bir boşluk koyabilirsiniz. Böylelikle bir öndeki boşluk karakterlerini atlayacak ve bir sonraki karakteri girdi olarak alacaktır. Aslında scanf fonksiyonu güvenli girdi alımı için çok tercih edilen bir fonksiyon değildir. Ya onunla ilgili çeşitli kontrollerin sağlanması gerekir ya da farklı bir alternatif fonksiyon kullanmak gerekir (fgets gibi). Ancak bu durum şimdilik bu yazının konusu değildir.
Bu do-while döngü yapısında bulunan bir başka önemli şey ise dizi indis operatöründe yer alan currentChar - 'a' ifadesidir. Bildiğiniz gibi char türü de aslında tamsayı tutan bir türdür ve ASCII karakterlerinin de bir tamsayı karşılıkları bulunmaktadır. Örneğin “a” harfinin ASCII karşılığı 97 olduğundan ve ardından gelen diğer harfler de 98, 99 ve 100 şeklinde ilerlediğinden, bu harfi (veya harfin sayısal karşılığını) gelen harften çıkararak bir indis numarası elde ederiz. Böylelikle alphabet dizisinde yer alan elemanlar direk olarak harflerin sayısını tutacak şekilde hizalanır. Bu oldukça sık kullanılan bir yöntemdir. Burada belki de ilk defa karşılaştığımız bir fonksiyon da islower fonksiyonudur. Bu fonksiyon girilen karakterin küçük harf olup olmadığını kontrol eder. Bu fonksiyon ctype.h başlık dosyasında yer alır ve prototipi şöyledir:
int islower(int c);
islower fonksiyonu parametresine aldığı değerin bir (İngiliz alfabesinden) küçük harf olup olmadığını kontrol eder. Eğer bu fonksiyona girilen argüman bir büyük harf ise veya harften başka bir karakterse, bu fonksiyon sıfır döner. Ancak bu değer bir küçük harf ise fonksiyon sıfır dışında bir değer ile geri döner. ctype.h dosyasında yer alan bu tip fonksiyonlar karakterler üzerinde işlem yapan fonksiyonlardır ve genellikle parametre yapıları ve geri dönüş değerleri bu şekildedir. Bu fonksiyonlarla ilgili ileride ayrı bir yazı paylaşacağım. Buradaki if kontrolünden anlayabildiğiniz gibi, kullanıcı küçük harf girdiğinde bu dizide onun indisine karşılık gelen elemanın değeri 1 arttırılacaktır. Peki bu döngünün sonlanma koşulu nedir? 26. satırda gördüğünüz gibi eğer kullanıcı “.” işaretini klavyeden girer ve Enter tuşuna basarsa, döngüden çıkılacaktır.
Şimdi programın diğer kısımlarından devam edelim. 29. satırda ünlü harflerin sayısını tutacak vowelCount isimli bir değişen tanımlıyoruz. Daha sonra ünlü harflerin indis numaralarını tuttuğumuz vowelIndexes isimli dizimiz üzerinde dolaşıyoruz. Buradan çıkan değerleri şu şekilde diğer bir dizimiz olan alphabet dizisine indis olarak verdiğimize dikkat edin:
vowelCount = alphabet[vowelIndexes[i]];
Bunu şu şekilde yapmamız da mümkündü:
unsigned int currentIndex = vowelIndexes[i]; vowelCount = alphabet[currentIndex];
Bu şekilde yazım bazı durumlarda daha okunabilir ve güvenliği sağlama açısından daha avantajlı olsa da ben burada indis operatörünün içerinde ne tür ifadeler kullanabileceğimizi göstermek istedim. Evet, bu operatöre değer olarak herhangi bir tamsayı dönen bir ifade yazmak mümkün olduğundan, yapabileceklerinizin sayısı oldukça fazladır. Bu for döngüsü ile girilen ünlü harflerin sayısını vowelCount isimli değişkende tutmuş oluyoruz. 36. ve 40. satırlar arasında yaptığımız işlem ise oldukça basit; bütün dizi elemanlarını toplayıp totalCount isimli değişkene yazıyoruz. 42. ve 43. satırlardaki hesaplama işlemlerinde de çok fazla açıklanacak şey olduğunu düşünmüyorum. Veri kaybı yaşamamak için dönüşümlere dikkat etmeniz gerektiğini tekrar hatırlatıyorum.
45. ve 46. satırlarda sonucu ekrana basarken, burada değişik olan bir şeyler görüyoruz. N%.2f ile ekrana bir ondalıklı değeri virgülden sonra iki basamak olacak şekilde basabileceğimizi zaten biliyoruz. Ancak bundan sonra gelen “%%” ne anlama geliyor? Bildiğiniz gibi “%” karakteri printf içerisindeki format String’i için özel bir anlama sahiptir. Ancak bazı durumlarda bu karakteri ekrana bastırmak isteyebiliriz. İşte bu karakteri bastrımak istediğimiz yere “%%” format belirtecini (format specifier) yazarak bunu yapabiliriz.
Bu yazıda dizilerle ilgili 3 adet örnek yaptım. Bu örneklerin her birinde diziler ile ilgili çeşitli kullanımları göstermeye çalıştım. Ayrıca bazı örneklerde daha önce görmediğimiz bazı özellikeri ve fonksiyonları da gösterdim. Diziler C’de oldukça sık kullanılan veri yapıları olduğundan onları bu şekilde farklı kullanımlarını görüp farklı örnekler yapmanız oldukça önem arz eder. Bu nedenle bu örnekler ile dizi kullanımını pekiştirmeyi istedim. Bir sonraki bölümde bazı özel durumları olan karakter dizilerini göstereceğim.


