Diziler
Bir önceki bölümün problemlerinden birisinde 5 tane değişken tanımlamış ve onlarla belirli işlemler yapmıştık: önce iki katlarını almıştık, sonra da beşe bölmüştük. O değişkenleri ayrı ayrı şöyle tanımlamıştık:
double sayı_1; double sayı_2; double sayı_3; double sayı_4; double sayı_5;
Bu yöntem her duruma uygun değildir, çünkü değişken sayısı arttığında onları teker teker tanımlamak, içinden çıkılmaz bir hal alır. Bin tane sayıyla işlem yapmak gerektiğini düşünün... Bin tane değişkeni ayrı ayrı sayı_1, sayı_2, ..., sayı_1000 diye tanımlamak hemen hemen olanaksız bir iştir.
Dizilerin bir yararı böyle durumlarda ortaya çıkar: diziler bir seferde birden fazla değişken tanımlamaya yarayan olanaklardır. Birden fazla değişkeni bir araya getirmek için en çok kullanılan veri yapısı da dizidir.
Bu bölüm dizi olanaklarının yalnızca bir bölümünü kapsar. Diğer olanaklarını daha ilerideki Başka Dizi Olanakları bölümünde göreceğiz.
bir bölümde göreceğiz. )Tanımlanması
Dizi tanımı değişken tanımına çok benzer. Tek farkı, dizide kaç değişken bulunacağının, yani bir seferde kaç değişken tanımlanmakta olduğunun, türün isminden sonraki köşeli parantezler içinde belirtilmesidir. Tek bir değişkenin tanımlanması ile bir dizinin tanımlanmasını şöyle karşılaştırabiliriz:
int tekDeğişken; int[10] onDeğişkenliDizi;
O iki tanımdan birincisi, şimdiye kadarki kodlarda gördüklerimiz gibi tek değişken tanımıdır; ikincisi ise 10 değişkenden oluşan bir dizidir.
Yukarıda sözü geçen problemdeki 5 ayrı değişkeni 5 elemanlı bir dizi halinde hep birden tanımlamak için şu söz dizimi kullanılır:
double[5] sayılar;
Bu tanım, "double türünde 5 tane sayı" diye okunabilir. Daha sonra kod içinde kullanıldığında tek bir sayı değişkeni sanılmasın diye ismini de çoğul olarak seçtiğime dikkat edin.
Özetle; dizi tanımı, tür isminin yanına köşeli parantezler içinde yazılan dizi uzunluğundan ve bunları izleyen dizi isminden oluşur:
tür_ismi[dizi_uzunluğu] dizi_ismi;
Tür ismi olarak temel türler kullanılabileceği gibi, programcının tanımladığı daha karmaşık türler de kullanılabilir (bunları daha sonra göreceğiz). Örnekler:
// Bütün şehirlerdeki hava durumlarını tutan bir dizi // Burada örneğin // false: "kapalı hava" // true : "açık hava" // anlamında kullanılabilir bool[şehirAdedi] havaDurumları; // Yüz kutunun ağırlıklarını ayrı ayrı tutan bir dizi double[100] kutuAğırlıkları; // Bir okuldaki bütün öğrencilerin kayıtları ÖğrenciBilgisi[öğrenciAdedi] öğrenciKayıtları;
Topluluklar ve elemanlar
Aynı türden değişkenleri bir araya getiren veri yapılarına topluluk adı verilir. Bu tanıma uydukları için diziler de toplulukturlar. Örneğin Temmuz ayındaki günlük hava sıcaklıklarını tutmak için kullanılacak bir dizi 31 tane double
değişkenini bir araya getirebilir ve double
türünde elemanlardan oluşan bir topluluk oluşturur.
Topluluk değişkenlerinin her birisine eleman denir. Dizilerin barındırdıkları eleman adedine dizilerin uzunluğu denir. "Eleman adedi" ve "dizi uzunluğu" ifadelerinin ikisi de sık kullanılır.
Eleman erişimi
Problemdeki değişkenleri ayırt etmek için isimlerinin sonuna bir alt çizgi karakteri ve bir sıra numarası eklemiştik: sayı_1
gibi... Sayıları hep birden bir dizi halinde ve sayılar
isminde tanımlayınca elemanlara farklı isimler verme şansımız kalmaz. Onun yerine, elemanlara dizinin erişim işleci olan []
ile ve bir sıra numarasıyla erişilir:
sayılar[0]
O yazım, "sayıların 0 numaralı elemanı" diye okunabilir. Bu şekilde yazınca sayı_1
ifadesinin yerini sayılar[0]
ifadesi almış olur.
Burada dikkat edilmesi gereken iki nokta vardır:
- Numara sıfırdan başlar: Biz insanlar nesneleri 1'den başlayacak şekilde numaralamaya alışık olduğumuz halde, dizilerde numaralar 0'dan başlar. Bizim 1, 2, 3, 4, ve 5 olarak numaraladığımız sayılar dizi içinde 0, 1, 2, 3, ve 4 olarak numaralanırlar. Programcılığa yeni başlayanların bu farklılığa dikkat etmeleri gerekir.
[]
karakterlerinin iki farklı kullanımı: Dizi tanımlarken kullanılan[]
karakterleri ile erişim işleci olarak kullanılan[]
karakterlerini karıştırmayın. Dizi tanımlarken kullanılan[]
karakterleri elemanların türünden sonra yazılır ve dizide kaç eleman bulunduğunu belirler; erişim için kullanılan[]
karakterleri ise dizinin isminden sonra yazılır ve elemanın sıra numarasını belirler:// Bu bir tanımdır. 12 tane int'ten oluşmaktadır ve her // ayda kaç gün bulunduğu bilgisini tutmaktadır int[12] ayGünleri; // Bu bir erişimdir. Aralık ayına karşılık gelen elemana // erişir ve değerini 31 olarak belirler ayGünleri[11] = 31; // Bu da bir erişimdir. Ocak ayındaki gün sayısını // writeln'a göndermek için kullanılmaktadır. writeln("Ocak'ta ", ayGünleri[0], " gün var.");
Hatırlatma: Ocak ayının sıra numarasının 0, Aralık ayının sıra numarasının 11 olduğuna dikkat edin.
İndeks
Elemanlara erişirken kullanılan sıra numaralarına indeks, elemanlara erişme işine de indeksleme denir.
İndeks sabit bir değer olmak zorunda değildir; indeks olarak değişken değerleri de kullanılabilir. Bu olanak dizilerin kullanışlılığını büyük ölçüde arttırır. Örneğin aşağıdaki kodda hangi aydaki gün sayısının yazdırılacağını ayNumarası
değişkeni belirlemektedir:
writeln("Bu ay ", ayGünleri[ayNumarası], " gün çeker");
ayNumarası
'nın 2 olduğu bir durumda yukarıdaki ifadede ayGünleri[2]
'nin değeri, yani Mart ayındaki gün adedi yazdırılır. ayNumarası
'nın başka bir değerinde de o aydaki gün sayısı yazdırılır.
Yasal olan indeksler, 0'dan dizinin uzunluğundan bir eksiğine kadar olan değerlerdir. Örneğin 3 elemanlı bir dizide yalnızca 0, 1, ve 2 indeksleri yasaldır. Bunun dışında indeks kullanıldığında program bir hata ile sonlanır.
Dizileri, elemanları yan yana duran bir topluluk olarak düşünebilirsiniz. Örneğin ayların günlerini tutan bir dizinin elemanları ve indeksleri şu şekilde gösterilebilir (Şubat'ın 28 gün çektiğini varsayarak):
indeksler → 0 1 2 3 4 5 6 7 8 9 10 11 elemanlar → | 31 | 28 | 31 | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31 |
Not: Yukarıdaki indeksleri yalnızca gösterim amacıyla kullandım; indeksler belleğe yazılmazlar.
İlk elemanın indeksi 0, ve Ocak ayındaki gün sayısı olan 31 değerine sahip; ikinci elemanın indeksi 1, ve Şubat ayındaki gün sayısı olan 28 değerine sahip; vs.
Sabit uzunluklu diziler ve dinamik diziler
Kaç eleman barındıracakları programın yazıldığı sırada bilinen dizilere sabit uzunluklu dizi; elemanlarının sayısı programın çalışması sırasında değişebilen dizilere dinamik dizi denir.
Yukarıda 5 sayı tanımlamak için kullandığımız sayılar
dizisi ve 12 aydaki gün sayılarını tutmak için kullandığımız ayGünleri
dizileri sabit uzunluklu dizilerdir; çünkü eleman sayıları baştan belirlenmiştir. O dizilerin uzunlukları programın çalışması sırasında değiştirilemez. Uzunluklarının değişmesi gerekse, bu ancak kaynak koddaki sabit olan değerin elle değiştirilmesi ve programın tekrar derlenmesi ile mümkündür.
Dinamik dizi tanımlamak sabit uzunluklu dizi tanımlamaktan daha kolaydır; dizinin uzunluğunu boş bırakmak diziyi dinamik yapmaya yeter:
int[] dinamikDizi;
Böyle dizilerin uzunlukları programın çalışması sırasında gerektikçe arttırılabilir veya azaltılabilir.
Eleman adedini edinmek ve değiştirmek için .length
Türlerin olduğu gibi dizilerin de nitelikleri vardır. Burada yalnızca bir tanesini göreceğiz. .length
dizideki eleman adedini bildirir:
writeln("Dizide ", dizi.length, " tane eleman var");
Ek olarak, .length
dinamik dizilerde dizinin uzunluğunu değiştirmeye de yarar:
int[] dizi; // boştur dizi.length = 5; // uzunluğu 5 olur
Bir dizi örneği
Bu bilgiler ışığında 5 değişkenli probleme dönelim ve onu dizi kullanacak şekilde tekrar yazalım:
import std.stdio; void main() { // Bu değişkeni döngüleri kaç kere tekrarladığımızı saymak // için kullanacağız int sayaç; // double türündeki beş elemandan oluşan sabit uzunluklu // bir dizi tanımlıyoruz double[5] sayılar; // Sayıları bir döngü içinde girişten alıyoruz while (sayaç < sayılar.length) { write("Sayı ", sayaç + 1, ": "); readf(" %s", &sayılar[sayaç]); ++sayaç; } writeln("İki katları:"); sayaç = 0; while (sayaç < sayılar.length) { writeln(sayılar[sayaç] * 2); ++sayaç; } // Beşte birlerini hesaplayan döngü de bir önceki // döngünün benzeridir... }
Gözlemler: Döngülerin kaç kere tekrarlanacaklarını sayaç
belirliyor: döngüleri, o değişkenin değeri sayılar.length
'ten küçük olduğu sürece tekrarlıyoruz. Sayacın değeri her tekrarda bir arttıkça, sayılar[sayaç]
ifadesi de sırayla dizinin elemanlarını göstermiş oluyor: sayılar[0]
, sayılar[1]
, vs.
Bu programın yararını görmek için girişten 5 yerine örneğin 20 sayı alınacağını düşünün... Dizi kullanan bu programda tek bir yerde küçük bir değişiklik yapmak yeter: 5 değerini 20 olarak değiştirmek... Oysa dizi kullanmayan programda 15 tane daha değişken tanımlamak ve kullanıldıkları kod satırlarını 15 değişken için tekrarlamak gerekirdi.
Elemanları ilklemek
D'de her türde olduğu gibi dizi elemanları da otomatik olarak ilklenirler. Elemanlar için kullanılan ilk değer, elemanların türüne bağlıdır: int
için 0, double
için double.nan
, vs.
Yukarıdaki programdaki sayılar
dizisinin beş elemanı da dizi tanımlandığı zaman double.nan
değerine sahiptir:
double[5] sayılar; // dizinin bütün elemanlarının // ilk değeri double.nan olur
Elemanların bu ilk değerleri dizi kullanıldıkça değişebilir. Bunun örneklerini yukarıdaki programlarda gördük. Örneğin ayGünleri
dizisinin 11 indeksli elemanına 31 değerini atadık:
ayGünleri[11] = 31;
Daha sonra da girişten gelen değeri, sayılar
isimli dizinin sayaç
indeksli elemanının değeri olarak okuduk:
readf(" %s", &sayılar[sayaç]);
Bazen elemanların değerleri, dizi kurulduğu anda bilinir. Öyle durumlarda dizi, atama söz dizimiyle ve elemanların ilk değerleri sağ tarafta belirtilerek tanımlanır. Kullanıcıdan ay numarasını alan ve o ayın kaç gün çektiğini yazan bir program düşünelim:
import std.stdio; void main() { // Şubat'ın 28 gün çektiğini varsayıyoruz int[12] ayGünleri = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; write("Kaçıncı ay? "); int ayNumarası; readf(" %s", &ayNumarası); int indeks = ayNumarası - 1; writeln(ayNumarası, ". ay ", ayGünleri[indeks], " gün çeker"); }
O programda ayGünleri
dizisinin elemanlarının dizinin tanımlandığı anda ilklendiklerini görüyorsunuz. Ayrıca, kullanıcıdan alınan ve değeri 1-12 aralığında olan ay numarasının indekse nasıl dönüştürüldüğüne dikkat edin. Böylece kullanıcının 1-12 aralığında verdiği numara, programda 0-11 aralığına dönüştürülmüş olur. Kullanıcı 1-12 aralığının dışında bir değer girdiğinde, program dizinin dışına erişildiğini bildiren bir hata ile sonlanır.
Dizileri ilklerken sağ tarafta tek bir eleman değeri de kullanılabilir. Bu durumda dizinin bütün elemanları o değeri alır:
int[10] hepsiBir = 1; // Bütün elemanları 1 olur
Temel dizi işlemleri
Diziler, bütün elemanlarını ilgilendiren bazı işlemlerde büyük kolaylık sağlarlar.
Sabit uzunluklu dizileri kopyalama
Atama işleci, sağdaki dizinin elemanlarının hepsini birden soldaki diziye kopyalar:
int[5] kaynak = [ 10, 20, 30, 40, 50 ]; int[5] hedef; hedef = kaynak;
Not: Atama işleminin anlamı dinamik dizilerde çok farklıdır; bunu ilerideki bir bölümde göreceğiz.
Dinamik dizilere eleman ekleme
~=
işleci, dinamik dizinin sonuna yeni bir eleman veya yeni bir dizi ekler:
int[] dizi; // dizi boştur dizi ~= 7; // dizide tek eleman vardır dizi ~= 360; // dizide iki eleman olur dizi ~= [ 30, 40 ]; // dizide dört eleman olur
Sabit uzunluklu dizilere eleman eklenemez:
int[10] dizi; dizi ~= 7; // ← derleme HATASI
Dinamik diziden eleman çıkartma
Dizi elemanları std.algorithm
modülündeki remove()
işlevi ile çıkartılırlar. Aynı elemanlara erişim sağlayan birden fazla dilim olabildiğinden remove()
dizideki eleman adedini değiştiremez. Tek yapabildiği, elemanları bir veya daha fazla konum sola kaydırmaktır. Bu yüzden, remove()
işlevinin sonucunun tekrar aynı dizi değişkenine atanması gerekir.
remove()
'un iki farklı kullanımı vardır:
- Çıkartılacak elemanın indeksini belirtmek. Örneğin, aşağıdaki kod indeksi 1 olan elemanı çıkartır.
import std.stdio; import std.algorithm; void main() { int[] dizi = [ 10, 20, 30, 40 ]; dizi = dizi.remove(1); // Tekrar dizi'ye atanıyor writeln(dizi); }
[10, 30, 40]
- Çıkartılacak elemanı ilerideki bir bölümde göreceğimiz bir isimsiz işlev ile belirtmek. Örneğin, aşağıdaki kod değerleri 42'ye eşit olan elemanları çıkartır.
import std.stdio; import std.algorithm; void main() { int[] dizi = [ 10, 42, 20, 30, 42, 40 ]; dizi = dizi.remove!(a => a == 42); // Tekrar dizi'ye atanıyor writeln(dizi); }
[10, 20, 30, 40]
Birleştirme
~
işleci iki diziyi uç uca birleştirerek yeni bir dizi oluşturur. Aynı işlecin atamalı olanı da vardır (~=
) ve sağdaki diziyi soldaki dizinin sonuna ekler:
import std.stdio; void main() { int[10] birinci = 1; int[10] ikinci = 2; int[] sonuç; sonuç = birinci ~ ikinci; writeln(sonuç.length); // 20 yazar sonuç ~= birinci; writeln(sonuç.length); // 30 yazar }
Eğer sol tarafta sabit uzunluklu bir dizi varsa, dizinin uzunluğu değiştirilemeyeceği için ~=
işleci kullanılamaz:
int[20] sonuç; // ... sonuç ~= birinci; // ← derleme HATASI
Atama işleminde de, sağ tarafın uzunluğu sol tarafa uymazsa program çöker:
int[10] birinci = 1; int[10] ikinci = 2; int[21] sonuç; sonuç = birinci ~ ikinci;
O kod, programın "dizi kopyası sırasında uzunluklar aynı değil" anlamına gelen hata ile çökmesine neden olur:
object.Error@(0): Array lengths don't match for copy: 20 != 21
Elemanları sıralama
std.algorithm.sort
işlevi elemanları küçükten büyüğe doğru sıralar. sort()
'tan yararlanabilmek için std.algorithm
modülünün eklenmesi gerekir. (İşlevleri daha sonraki bir bölümde göreceğiz.)
import std.stdio; import std.algorithm; void main() { int[] dizi = [ 4, 3, 1, 5, 2 ]; sort(dizi); writeln(dizi); }
Çıktısı:
[1, 2, 3, 4, 5]
Elemanları ters çevirmek
std.algorithm.reverse
elemanların yerlerini aynı dizi içinde ters çevirir; ilk eleman sonuncu eleman olur, vs.:
import std.stdio; import std.algorithm; void main() { int[] dizi = [ 4, 3, 1, 5, 2 ]; reverse(dizi); writeln(dizi); }
Çıktısı:
[2, 5, 1, 3, 4]
Problemler
- Yazacağınız program önce kullanıcıdan kaç tane sayı girileceğini öğrensin ve girişten o kadar kesirli sayı alsın. Daha sonra bu sayıları önce küçükten büyüğe, sonra da büyükten küçüğe doğru sıralasın.
Burada
sort
vereverse
işlevlerini kullanabilirsiniz. - Başka bir program yazın: girişten aldığı sayıların önce tek olanlarını sırayla, sonra da çift olanlarını sırayla yazdırsın. Özel olarak -1 değeri girişi sonlandırmak için kullanılsın: bu değer geldiğinde artık girişten yeni sayı alınmasın.
Örneğin girişten
1 4 7 2 3 8 11 -1
geldiğinde çıkışa şunları yazdırsın:
1 3 7 11 2 4 8
İpucu: Sayıları iki ayrı diziye yerleştirmek işinize yarayabilir. Girilen sayıların tek veya çift olduklarını da aritmetik işlemler sayfasında öğrendiğiniz
%
(kalan) işlecinin sonucuna bakarak anlayabilirsiniz. - Bir arkadaşınız yazdığı bir programın doğru çalışmadığını söylüyor.
Girişten beş tane sayı alan, bu sayıların karelerini bir diziye yerleştiren, ve sonunda da dizinin elemanlarını çıkışa yazdıran bir program yazmaya çalışmış ama programı doğru çalışmıyor.
Bu programın hatalarını giderin ve beklendiği gibi çalışmasını sağlayın:
import std.stdio; void main() { int[5] kareler; writeln("5 tane sayı giriniz"); int i = 0; while (i <= 5) { int sayı; write(i + 1, ". sayı: "); readf(" %s", &sayı); kareler[i] = sayı * sayı; ++i; } writeln("=== Sayıların Kareleri ==="); while (i <= kareler.length) { write(kareler[i], " "); ++i; } writeln(); }