Diziler(Arrayler)
Kodumuzda aynı türden bir veya iki farklı eleman kullanacaksak standart değişkenler yeterli olacaktır. Ancak bu sayı artarsa değişken kullanımı pek pratik olmamaya başlar. İşte bu nokta, dizilerin devreye girdiği yerdir.
Mesela 20 bölgesi olan bir bankada çalışıyorsanız her bölge için bir değişken tanımlamak yerine bölge adında bir dizi tanımlanıp, her bölge bu diziye elaman olarak atanabilir. Karşılaştırmaya bakalım:
'pratik olmayan yöntem
Dim bölge1 As String
Dim bölge2 As String
.....
Dim bölge20 As String
Bir de bunun şube versiyonu var, ki şube sayısı 1000 civarındaysa değişken tanımlarken ömrünüzden 1 yıl gider herhalde.
Peki şimdi nedir bu diziler, nasıl tanımlanır, başka neler yapılır, bunlara bakalım.
Genel bir bilgi edindikten sonra buraya da bakmanızı tavsiye ederim. Özellikle başka programlama dillerine aşinaysanız aradaki farkları ve benzerlikleri görmek için faydalı olacağını düşünüyorum.
Tanımlama(Declaration)
Diziler, normal değişkenler gibi Dim ifadesi ile tanımlanırlar, ancak değişken adının yanında fazladan yuvarlak parantezlere sahiptirler. Parantezler; dizi boyutu baştan belliyse boyut numarasını içerirler, belli değilse boş bırakılır ve sonradan tanımlanır. Dizi boyutu baştan belirlenen dizilere Statik dizi, boyutu baştan belirtilmeyen dizilere Dinamik dizi denir.
Dim diziadı(boyut) As Tip
Tip belirtilmezse tıpkı normal değişkenlerde olduğu gibi dizimiz Variant tipli bir dizi olur ve her tür değişkeni karışık olarak depolayabilir.
Dim bölgeler(19) As String 'boyut belirtildi
Dim şubekod() As String 'boyut belirtilmedi
Bu örnekte bölge sayısı sabit olduğu için boyut baştan belirtildi, yani Statik tanımlandı ancak bir bankada şube sayısı çok sık değişebilir; yeni şubeler açılır, mevcut şubeler kapanır, şubeler birleşir;o yüzden ona baştan bir boyut belirtmesek de olur, yani Dinamik tanımlandı. Bu dinamik dizilere aşağıda ayrıca bakıyor olacağız.
Boyut(elaman sayısı), index, alt/üst limitler
Dizi için tanımlanan eleman sayısına boyut denir. Boyut belirtmenin de iki yolu vardır.
- Dim dizi(x to y) As Tip
- Dim dizi(y) As Tip
İlk yöntemde dizinin kaçıncı indexten başlayıp kaçta biteceği belirtilirken ikinci yöntemde ise doğrudan kaçta biteceği belirtilir, kaçta başlayacağı ise Option Base ifadesinin kullanılıp kullanılmadığına göre değişir. İndeksin biteceği son yere üst sınır denir ve bu sınır Ubound fonksiyonu ile elde edilir.
Dim segmentler(4) As String
segmentler(0)="Bireysel"
segmentler(1)="Birebir"
segmentler(2)="Kobi"
segmentler(3)="Ticari"
'segmentler(4)="Özel"
For i=0 to UBound(segmentler)
MsgBox segmentler(i)
Next i
Yukardaki örnekte gördüğünüz üzere son indexli elamana değer atanmadı, bu yüzden de boş olarak göründü. Anlayacağınız üzere, dizideki tüm elemanlara değer atanmak zorunda değildir. Yani, bir dizide eleman sayısı ile dolu(değer atanmış) eleman sayısı aynı olmayabilir.
Varsayılan olarak dizilerin ilk eleman indeksi 0'dır. Ancak bu index Option Base 1 ifadesi ile 1 yapılabilir, ki ben bunu zorunda olmadığınız sürece çok kullanmanızı tavsiye etmiyorum. Veya yukarda 1. yöntemdeki gibi dizi boyutu baştan 1 to y şeklinde belirtilerek de başlangıç indexi 1 yapılabilir. Yani şu iki ifade tamamen özdeştir.
Option Base 1
Dim bolgeler(20) As String
Dim bolgeler(1 to 20) As String
İndex numarası açısından olmasa da eleman sayısı açısından şu dizi de yukardakilerle aynı kapasitededir, yani hepsi de 20 eleman içerir.
Dim bolgeler(19) As String
Dizi tanımının yönteminden ve Option Base kullanımından bağımsız olarak alt indexin ne olduğu ise LBound fonksiyonu ile elde edilir.
Option Base 1 kullanımını tavsiye etmiyoruz dedik, zira ilgili modüldeki tüm prosedürler için indexi 1den başlatır. Bununla beraber bazı dizileri bilinçli olarak 1 nolu indeksten başlatmak gerekebilir. Mesela ayno isimli bir dizimiz olduğunu düşünün, ayno(1) diyince Ocak ayını ele almak, anlaşılırlık açısından daha makbuldür, pek tabiki ayno(1) içinde Şubat ayı da depolanabilir ancak bu yol, konuşma diline biraz aykırılık teşkil edeceği için bunu ayno(1 to 12) şeklinde tanımlamayı tercih etmek daha akıllıca olacaktır.
Bu arada x to y yönteminde x olarak 1'den büyük değerler de belirtilebilir ama bunun pratikte çok kullanıldığı görülmez.
Çok boyutlu dizilerde eleman sayısı, boyutlardaki elemanların çarpımına eşittir.(Bunlar aşağıda ayrıca detaylı incelenecek)
Özetleyecek olursak;
Dim b(10) As String şeklinde tanımlanan 1 boyutlu bir dizide;
Aranan | Yöntem | Sonuç |
---|---|---|
Alt limit | LBound(b) | 0 |
Üst limit | UBound(b) | 10 |
Eleman sayısı | Ubound(b)-LBound(b)+1 | 11 |
Dim b(1 to 10) As String şeklinde tanımlanan 1 boyutlu bir başka dizide;
Aranan | Yöntem | Sonuç |
---|---|---|
Alt limit | LBound(b) | 1 |
Üst limit | UBound(b) | 10 |
Eleman sayısı | Ubound(b)-LBound(b)+1 | 10 |
Dim b(10,5) As Integer şeklinde tanımlanan 2 boyutlu bir dizide;
Aranan | Yöntem | Sonuç |
---|---|---|
1.boyutun Alt limiti | LBound(b,1) | 0 |
1.boyutun Üst limiti | UBound(b,1) | 10 |
2.boyutun Alt limiti | LBound(b,2) | 0 |
2.boyutun Üst limiti | UBound(b,2) | 5 |
1.boyutun Eleman sayısı | Ubound(b,1)-LBound(b,1)+1 | 11 |
2.boyutun Eleman sayısı | Ubound(b,2)-LBound(b,2)+1 | 6 |
Dizideki Eleman sayısı | 1. boyut elaman sayısı*2.boyut eleman sayısı | 66 |
DIKKAT:Bir dizinin boyutu baştan birkez belirtilirse bir daha asla değişmez. Boyutu değişen dizilere ise aşağıda değineceğiz.
Elemanlara değer atama
İlk değer atama
Dizi elamanlarına index numaralarıyla ulaşırız. Normalde bunlara tek tek değer atamak genelde pratikte karşılaşılan bir durum değildir, belki küçük boyutlu dizilerde olabilir ancak genelde bir döngüsel yapı ile bir hücre grubundan değer okuyup onları atamak şeklinde olmaktadır. Aksi halde kodumuz oldukça uzayacaktır.
Dim Segment(3) As String
Segment(0) = "Bireysel"
Segment(1) = "Birebir"
Segment(2) = "Kobi"
Segment(3) = "Ticari"
Döngüyle atamaya örnek olarak da şunu verebiliriz;A1:A20 arasındaki bölge kodlarını bölge dizisine atıyoruz.
For i=1 to 20
bölge(i)=Cells(i,1).Value2
Next i
Değer atanmamış elemanlar ve Erase fonksiyonu
Henüz değer atanmamış dizi elemanları, dizinin tipine göre default değerlerini alırlar. Bunlar;
- String diziler için sıfır uzunluklu metin, yani ""
- Nümerik diziler için 0
- Variant diziler için Empty
- Object diziler için Nothing
Statik dizilerde elemanların hepsini tek seferde varsayılan değerlerine döndürmek için Erase fonksiyonu kullanılır. Yani dizi elemanlarının taşıdığı değerler boşaltılır. Ancak bunlar hala hafızada yer kaplamaya devam ederler.
Sub erasestatik()
Dim segment(2) As String
segment(0) = "bireysel"
segment(1) = "birebirt"
segment(2) = "kobi"
Erase segment()
Debug.Print segment(1) '"" döndürür
End Sub
Array fonksiyonu
Elle tek tek tanımlama yapılması gereken durumlarda, bir diğer eleman tanımlama yöntemi de Array fonksiyonunu kullanmaktır, ki bu sadece elaman değeri atama değil aynı zamanda diziyi tanımlama yöntemidir de. Zira bu şekilde tanımlanan diziler Variant tipte olurlar ve bildiğiniz gibi Dim ile tanımlanmayan tüm değişkenler Variant kabul edilirler. Bu sayede elemanlar tanımlanırken dizi de yaratılmış olur. Ama biz yine de iyi bir programcı olup dizimizi Dim ile tanımlayalım. Bu yöntemle tanımlanan dizilerde başlangıç indeksi her zaman 0 olur. Yine, bu yöntemle yaratılan diziler 1 boyutlu olur
Dim Segment As Variant
Segment = Array("Bireysel","Birebir","Kobi","Ticari") 'tek satırda tanımlama imkanı
Debug.Print Segment(2) 'Kobi yazar
Bu yöntemin pratik bir kullanımı "ayisim" gibi bir fonksiyon şeklinde kendini gösterebilir. Fonksiyonlarla kafanızı karıştırmamak için şimdilik Sub prosedür şeklinde örneğe görelim.
Sub ayisimornek()
Dim ayisim As Variant
ayisim = Array("", "Ocak", "Şubat") 'ilk ayı boş geçtim, index 0 olduğu için
Debug.Print ayisim(2)
End Sub
Dizi içinde dolaşma ve elemanlara erişim
Dizilere eleman atama işini döngülerle yaptığımız gibi, dizi elamanlarını okumayı da yine genelde döngülerle yaparız. Ender olarak kod içinde bir yerde bir index numarası temin edip onu doğrudan da kullandığımız da olur.
For i=1 to 20
Cells(i,1).Value2=bölge(i)
Next i
Daha şık şekli aşağıdaki gibi olabilir, hatta sadece şık değil aynı zamanda güvenlidir de.
For i=LBound(bölge) to UBound(bölge)
Cells(i,1).Value2=bölge(i)
Next i
Evet, biraz daha fazla kod yazmış olduk ama güvenlik ön planda olacaksa hard-code(rakamı doğrudan belirterek) alt-üst limit vermek yerine bu fonksiyonlarla vermek daha verimlidir. Hard-code yazıldığında, bölge sayısı 1 arttığında 20 yerine 21 yapmayı unutursanız kodunuz yine hata almadan çalışır ancak eksik çalışmış olur, ve belki akabinde çalışan kodlarla bölgelere otomatik mail gidiyorsa, son bölgeye hiç mail gitmemiş olur. Bir de içiçe bir dizi varsa ve her seviye için üst limit değişiyorsa Ubound'ı kullanmak zaten zaruri olacaktır.
Üstelik bu yazım şekli alt limitin 0 mı 1 mi olduğunu da pek önemsemez . Böylece Option Base var mıydı yok muydu, diziyi (1 to 20) şeklinde mi yoksa (20) şeklinde mi tanımladığınızı hatırlamak zorunda kalmazsınız.
DIKKAT:Dizi içinde For döngüsü ile dolaşırken diziye eleman atama işlemi sadece For i=1 to 10 şeklindeki basit For döngüsü ile yapılabilirken, eleman değerini okuma işlemi ise hem basit For ile hem de For Each ile yapılabilir.
Mesela aşağıdaki gibi bir kullanım sorunsuz çalışırken,
Dim Bölge(1 to 20) As String
For i=1 to 20
bölge(i)=Cells(i,1).Value2
Next i
bu kod da hata vermeden çalışır ancak eleman değerlerinin değişmediğini görebilirsiniz. (Eğer daha önce bir değer atanmadıysa içleri boş görünecektir.)
Dim Bölge(1 To 20) As String
For Each b In Bölge
b = ActiveCell.Value2
ActiveCell.Offset(1, 0).Select
Next b
Yukarıdaki örnekler hep statik dizi örnekleriydi (Array fonksiyonu ile tanımlananlar hariç). Dizimizin boyutunu baştan bilmiyorsak ve kodun gidişatına göre değişken bir şekilde karşımıza çıkma durumu varsa, diziyi boyutsuz yani dinamik tanımlarız ve zamanı geldiğinde boyutunu belirtiriz. Bunu da ReDim ifadesi ile yaparız. Aslında yaptığımız şey, yeni bir statik dizi yaratmaktır. Zira ReDim kullanıp da yeni boyutunu verdiğimiz diziyi yine yeterli bulmazsak tekrar boyut değiştirebiliriz.
Dim şubeler() As String
....
....
ReDim şubeler(800)
Statik dizilerde, genelde kaynak israfı söz konusudur, zira baştan olası en yüksek değere göre boyut belirlenir ve bu boyutların hepsi çoğu zaman kullanılmaz. Tabii ki bölge sayısı gibi kesin olarak bilinen ve hepsi kullanılan durumlar istisnadır. Bu nedenle genel olarak statik değil dinamik dizi kullanılması önerilir.
Erase Fonksiyonu
Dinamik dizilerde Erase fonksiyonu statik dizilerden farklı çalışır. Statik dizilerde Erase, elemanları default değerlerine atarken dinamik dizide diziyi boyutsuzlaştırır, yani bir nevi siler. Diziyi tekrar kullanmak isterseniz ReDim ile yeniden boyutlandırmanız gerekir.
Diyelim ki ReDim ile 10 elemanlık bir boyutlandırma yaptınız ancak öyle bir nokta geldi ki, 10 eleman yetmiyor, yani boyut artırmanız lazım, böyle bir durumda ReDim Preserve ifadesini kullanırız. Preserve demezsek dizi yine yeni boyuta göre boyutlanır ancak önceki elemanlar silinmiş olur. Preserve ile ilk 10 elemanın değerini de korumuş oluruz.
Boyut artırma durumlarında ReDim'in bir veya iki kez kullanılması önerilir. Birkaç kez boyut artırma ihtiyacı oluyorsa belki dizi değil de Collection kullanmak faydalı olabilir.
NOT: Variant olarak tanımlanan değişkenler doğası gereği dinamiktirler. (Variant tanımlanan diziler ise statiktir)
Dim statikVar As Variant 'standart Variant değişken
Dim dinamikVar(5) As Variant 'Variant tipli dizi
Variant Değişken vs Variant Dizi
Variantlarla ilgili detaylı bir örnek aşağıda bulunmaktadır. Bunun oldukça aydınlatıcı olduğunu düşünüyorum.
Sub variantlı()
Dim aylar1 As Variant 'içine istediğiniz tipte değer atayabileceğiniz bir DEĞİŞKENDİR, buna dizi de dahildir,
'ama dizi atayana kadar dizi değildir ve IsArray testi false döner. Ubound da kullanamazsınız
Dim aylar2() As Variant 'Parantez olduğu için bu kesinlikle DİZİDİR, IsArray true döner
'***öncelikle parantezsiz olan yani değişken olan Aylara bakalım*****
aylar1 = 1 'number atandı
Debug.Print aylar1
'Debug.Print aylar1(1) 'hata verir, çünkü içeriği henüz bir dizi değil.
Debug.Print IsArray(aylar1) 'false
'Debug.Print UBound(aylar1) 'çalışmaz çünkü şuan için dizi değil
aylar1 = "volki" 'string
Debug.Print IsArray(aylar1) 'yine false
Debug.Print aylar1
aylar1 = Now 'tarih
Debug.Print IsArray(aylar1) 'hala false
Debug.Print aylar1
aylar1 = Array("hi", "world", "naber") 'dizi
Debug.Print IsArray(aylar1) 'artık true, çünkü Array function ile array yaptık
Debug.Print UBound(aylar1)
Debug.Print aylar1(2) 'artık hata vermez, çünkü içeriği bir dizi
aylar1 = Array(Array("hi", "world", "naber"), Array(1, "ss", 3)) 'dizi dizisi
Debug.Print UBound(aylar1)
Debug.Print aylar1(1)(1)
aylar1 = Range("a1:a5").Value ' range atadık, 2 boyutlu dizi
Debug.Print UBound(aylar1) '5 döner
Debug.Print aylar1(2, 1) '2.satırdaki değer döner
'***şimdi de Dizi olan Aylara bakalım
Debug.Print IsArray(aylar2)
'Debug.Print UBound(aylar2) hata verir, zira henüz boyut belli değil, ya ReDim yapılmalı ya da Array ile değer atanmalı
ReDim aylar2(3)
Debug.Print UBound(aylar2)
aylar2 = Array("OCAK", "ŞUBAT", "MART", "NİSAN", "MAYIS", "HAZİRAN", "TEMMUZ", "AĞUSTOS", "EYLÜL", "EKİM", "KASIM", "ARALIK")
Debug.Print UBound(aylar2)
Debug.Print aylar2(2)
Debug.Print UBound(aylar2)
'şimdi de farklı data tiplerinde atama yapıyoruz, Variant olduğu için sorun olmuyor
aylar2 = Array("OCAK", 2, 3, "30.04.2015", Now + 60, "HAZİRAN", "TEMMUZ", "AĞUSTOS", "EYLÜL", "EKİM", "KASIM", "ARALIK")
Debug.Print aylar2(4)
End Sub
Şimdiye kadar gördüğümüz dizilerin çoğu tek boyutlu idi, ama ihtiyacımıza göre birden fazla boyutlu diziler de oluşturabiliyoruz. Pratikte üçten fazla boyutun kullanıldığını ben açıkçası ne duydum ne de gördüm. Şahsen kendi kodlarımda kullandığım dizilerin büyük kısmı tek boyutludur, birkaç tane 2 boyutlu bir tane de 3 boyutlu dizim bulunmakta. 3 boyutlu dizi örneğinde aynı zamanda Dictionary de kullandığım için bu örneği o bölümde ele alacağız.
Şimdi 2 boyutlu bir dizi kullanım örneğine bakalım. Excel sayfalarının kendileri zaten satır ve sütunlardan oluşmakta olup 2 boyutludurlar ve bu durumu 2 boyutlu dizilerle birlikte çok sık kullanıyor olacağız.
Mesela 10 bölgesi olan bir bankada her bölgenin bir Bireysel bir de Ticari müdürü olduğunu düşünelim. Bunları bir diziye atama işlemi nasıl oluyor ona bakalım.
İngilizcede Array of Array ve Jagged Array olarak kullanılan bu dizi türünü Variant tipte dizi tanımlayarak elde ederiz. Bilindiği gibi Variant veri tipi içinde her şeyi tutabilir, buna diziler de dahildir. Ancak buradaki ayrıma dikkat etmek lazım, klasik Variant bir değişken tanımlamıyoruz, Variant tipli bir dizi tanımlıyoruz. Aradaki ayrımı görmek için şu iki satıra bakalım:
Dim d As Variant 'bu Variant tipli klasik bir değişkendir
Dim d() As Variant 'bu ise Variant tipli bir dizidir
İçiçe dizilerin tanımlaması iki ayrı dizi şeklinde olur, ama kullanımı dizi(x)(y) şeklindedir. Yani aslında bu dizi türü tek boyutludur ama içerdiği her eleman da bir başka dizidir.
Dim dışdizi() As Variant 'ana dizi Variant olmalıdır
Dim içdizi() As String ' Alt dizinin Variant olması gerekmez
'iç diziyi dış diziye eleman olarak atama
dışdizi(n) = içdizi
'nihai kullanım şekli
Debug.Print dışdizi(x)(y)
Bu dizilerin kullanımı çok boyutlu dizilere benzer ancak, kullanım ihtiyacı ve şekli küçük farklar göstermektedir. Şöyle ki, çok boyutlu dizilerde tüm boyutlar için hafızada yer ayrılır, belki o boyutlardan bazısı hiç kullanılmayacak bile. Diyelim 20 bölgeli bir banka var, bazı bölgelerde 40 şube varken bazısında 15 şube bulunabilir. Şimdi Şubeler(1 to 20, 1 to 40) şeklinde 2 boyutlu bir dizi tanımlarsak bazı bölgelerde bazı boyutlar israf olacak. İşte içiçe dizilerde bu israf olmuyor. Örneğin 2. bölgenin şube sayısı 18 ise, buradaki 18. şubeyi tanımlayacak son eleman bölge(2)(18) oluyor, bölge(2)(19) şeklinde bir tanımlama yapılmıyor.
Mesela aşağıdaki tabloya göre içiçe dizimizi oluşturalım.
İngilizcede Array of Array ve Jagged Array olarak kullanılan bu dizi türünü Variant tipte dizi tanımlayarak elde ederiz. Bilindiği gibi Variant veri tipi içinde her şeyi tutabilir, buna diziler de dahildir. Ancak buradaki ayrıma dikkat etmek lazım, klasik Variant bir değişken tanımlamıyoruz, Variant tipli bir dizi tanımlıyoruz. Aradaki ayrımı görmek için şu iki satıra bakalım:
Dim d As Variant 'bu Variant tipli klasik bir değişkendir
Dim d() As Variant 'bu ise Variant tipli bir dizidir
İçiçe dizilerin tanımlaması iki ayrı dizi şeklinde olur, ama kullanımı dizi(x)(y) şeklindedir. Yani aslında bu dizi türü tek boyutludur ama içerdiği her eleman da bir başka dizidir.
Dim dışdizi() As Variant 'ana dizi Variant olmalıdır
Dim içdizi() As String ' Alt dizinin Variant olması gerekmez
'iç diziyi dış diziye eleman olarak atama
dışdizi(n) = içdizi
'nihai kullanım şekli
Debug.Print dışdizi(x)(y)
Bu dizilerin kullanımı çok boyutlu dizilere benzer ancak, kullanım ihtiyacı ve şekli küçük farklar göstermektedir. Şöyle ki, çok boyutlu dizilerde tüm boyutlar için hafızada yer ayrılır, belki o boyutlardan bazısı hiç kullanılmayacak bile. Diyelim 20 bölgeli bir banka var, bazı bölgelerde 40 şube varken bazısında 15 şube bulunabilir. Şimdi Şubeler(1 to 20, 1 to 40) şeklinde 2 boyutlu bir dizi tanımlarsak bazı bölgelerde bazı boyutlar israf olacak. İşte içiçe dizilerde bu israf olmuyor. Örneğin 2. bölgenin şube sayısı 18 ise, buradaki 18. şubeyi tanımlayacak son eleman bölge(2)(18) oluyor, bölge(2)(19) şeklinde bir tanımlama yapılmıyor.
Mesela aşağıdaki tabloya göre içiçe dizimizi oluşturalım.
Sub içiçedizi()
Dim bolge(1 To 3) As Variant 'dış dizi
Dim subeler() As String 'iç dizi
Dim i As Integer, s As Integer, k As Integer
For i = 1 To 3
ReDim subeler(1 To Cells(i + 1, 2)) 'iç dizinin boyutunu ayarlıyoruz, her bölgede bu boyut değişecek
'iç diziyi oluşturalım
For s = 1 To UBound(subeler)
subeler(s) = Cells(s + k + 1, 6)
Next s
k = k + s - 1 'satır sayısı resetlenmesin diye geçici bir değişkene atıyorum
'iç diziyi dış dizinin ilgili elemanına atayalım
bolge(i) = subeler
Next i
Debug.Print bolge(1)(5) 'Şube5
Debug.Print bolge(2)(2) 'Şube8
Debug.Print bolge(3)(4) 'hata verir, çünkü 3.bölgenin 4.şubesi yok
End Sub
Dizi dizisiyle yapılabilen işlemler "Collection of Collection" veya "Dictionary of Dictionary" yapılarıyla da yapılabilir. Bunları bir sonraki bölümde görüyor olacağız. Bu arada hepsinin avantaj ve dezavantajını anlatan güzel bir sayfa var, ona buradan ulaşabilirsiniz.
Bir hücre grubunu diziye atama (Sayfadan okuma) Dizileri, bir hücre grubundan hızlı veri okuma ve yazma amacıyla da kullanırız. Bu kullanım şekli, özellikle ilgili veri kümesi üzerinde bir güncelleme (belli bir rakamla çarpmak gibi) yapmak istediğinizde idealdir.
Tabi bu amaçla kullanmak istediğimizde standart dizi tanımı yerine Variant olarak tanımlarız, ve tanımladığımız dizi 2 boyutlu bir dizi olur. Zira bir sayfaya baktığınızda gördüğünüz şey satır ve sütunlardan oluşan iki boyutlu bir dizidir. Böyle bir dizide de ilk boyut satır ikinci boyut sütun olur. Bu noktada karışıklığa neden olan bir konu vardır ki o da şudur: Söz konusu hücre grubu tek kolonluk bir alandan oluşuyorsa bile iki boyutludur. İlk boyut satır sayısı kadar elemanlı, ikinci grup da sütun sayısı olan 1 elemanlıdır. Bu tür dizilerde indeks 0'dan değil 1'den başlar. (Tek boyutlu variant dizilerde ise 0'dan başlıyordu)
Mesela aşağıdaki kod ile A1:A1000 hücrelerindeki değerler 100 ile çarpılır.
Bir hücre grubunu diziye atama (Sayfadan okuma) Dizileri, bir hücre grubundan hızlı veri okuma ve yazma amacıyla da kullanırız. Bu kullanım şekli, özellikle ilgili veri kümesi üzerinde bir güncelleme (belli bir rakamla çarpmak gibi) yapmak istediğinizde idealdir.
Tabi bu amaçla kullanmak istediğimizde standart dizi tanımı yerine Variant olarak tanımlarız, ve tanımladığımız dizi 2 boyutlu bir dizi olur. Zira bir sayfaya baktığınızda gördüğünüz şey satır ve sütunlardan oluşan iki boyutlu bir dizidir. Böyle bir dizide de ilk boyut satır ikinci boyut sütun olur. Bu noktada karışıklığa neden olan bir konu vardır ki o da şudur: Söz konusu hücre grubu tek kolonluk bir alandan oluşuyorsa bile iki boyutludur. İlk boyut satır sayısı kadar elemanlı, ikinci grup da sütun sayısı olan 1 elemanlıdır. Bu tür dizilerde indeks 0'dan değil 1'den başlar. (Tek boyutlu variant dizilerde ise 0'dan başlıyordu)
Mesela aşağıdaki kod ile A1:A1000 hücrelerindeki değerler 100 ile çarpılır.
Sub rakamguncelle()
Dim rakamlar As Variant 'Dizi tanımı
rakamlar = Range("A1:A10000").Value 'burada hücreden diziye okuma yaptık
For i = LBound(rakamlar) To UBound(rakamlar) 'i'ler satır boyutudur
rakamlar(i, 1) = rakamlar(i, 1) / 100 '1 ise sütun boyutu
Next i
Range("A1:A10000").Value = rakamlar 'üzerine de yazabiliriz başka bir yere de yazabilirdik
End Sub
Alanımız tek kolon değil de birden çok kolondan oluşuyorsa sütun için de bir iç döngü daha eklememiz gerekir. Bu sefer hücre grubumuzu dinamik düşünelim, buna göre kodumuz aşağıdaki gibi olacaktır.
Dim rakamlar As Variant 'Dizi tanımı
rakamlar = Selection.Value 'burada hücreden diziye okuma yaptık
For i = LBound(rakamlar) To UBound(rakamlar) 'i'ler satır boyutudur
For j = LBound(rakamlar, 2) To UBound(rakamlar, 2) 'j'ler sütun boyutudur
rakamlar(i, j) = rakamlar(i, j) * 1000
Next j
Next i
Selection.Value = rakamlar
Tek Boyutlu Bir Diziyi Sayfaya Yazdırma
Yazdırma işlemi yapmak için Resize özelliği ile hücreleri yeniden boyutlandırmayı bilmeniz gerekiyor. Resize hakkında bilgi sahibi değilseniz buradan bakıp önbilgi edinebilirsiniz.
Bu işlem için bir Range nesnesi yaratırız ve sonra hedef hücreyi belirleriz. Sonra bu hücreyi Resize ile dizi boyutu kadar genişletiriz.
' Yatay yazma (Tek satırlı çok kolonlu)
Dim dizi(1 To 3) As Integer
Dim Hedef As Range
dizi(1) = 10
dizi(2) = 20
dizi(3) = 30
Set Hedef = Range("A1").Resize(1, UBound(dizi))
Hedef.Value = dizi
Eğer dizimizi dikey şekilde yani tek kolon çok satırda yazmak isteseydik, Resize argümanlarını ters çevirmemiz ve Transpose metodunu kullanmamız gerekirdi.
' Dikey yazma (Tek kolon çok satır)
Dim dizi(1 To 3) As Integer
Dim Hedef As Range
dizi(1) = 10
dizi(2) = 20
dizi(3) = 30
Set Hedef = Range("A1").Resize(UBound(dizi), 1)
Hedef.Value = WorksheetFunction.Transpose(dizi)
Dizileri Dizilere Atama
Gelişmiş programlama dillerinde olduğunun aksine VBA'de bir diziyi tek seferde başka bir diziye kopyalama/atama yöntemi bulunmamaktadır. Yani şöyle bir kod çalışmaz.
Dim A(1 To 3) As Long
Dim B(1 To 3) As Long
A(1) = 100000
A(2) = 200000
A(3) = 300000
B = A
Peki nasıl yapılır? Tabii ki döngülerle;
Dim A(1 To 3) As Long
Dim B(1 To 3) As Long
A(1) = 100000
A(2) = 200000
A(3) = 300000
For i = 1 To 3
B(i) = A(i)
Next i
Debug.Print B(2)
Dizilerin diğer kullanma şekilleri
Variant Dizileri Variant Dizilere Atama
Dizileri başka dizilere atayamıyoruz ancak içi dizi olan bir Variantı başka bir Varianta atayabiliyoruz. Zira, bu işlem aslında dizi atama değil değişken atamadır.
Dim A As Variant
Dim B As Variant
A = Array(100000, 200000, 30000)
B = A 'atama başarılıdır
Debug.Print B(2) '30000 yazar
Range atanmış Variant dizileri normal dizilere atama
Bir hücre grubunu doğrudan bir variant diziye atama şeklini yukarda görmüştük, ama bir şekilde bunları tek boyutlu bir diziye atamanız gerekirse bunu yapmanın yolu döngü kullanmaktır. Tabi istenirse arada hiç variant dizi olmadan da doğrudan ilgili alanlar üzerinden de geçilebilir ancak, eğer bir şekilde ilgili alan kod içinde silindiyse, veya büyük bir alan ise peformans sorunu yaşamamak için Variant diziden normal diziye atamak daha verimli olacaktır, zira diziler üzerindeki işlemler range'lere göre çok daha hızlıdır.
Şimdi aşağıdaki gibi bir hücre grubu olsun, bunu önce Varianta sonra da normal bir diziye atayalım.
Sub cokboyutlu_variant_to_dizi()
Dim var As Variant
Dim dizi() As String
Dim i As Integer, j As Integer
' B2:D10 hücre aralığındaki değerleri Variant diziye okuma
var = Range("B2:D10").Value2
' String dizisini Variant dizinin boyutuna göre yeniden boyutlandırma
ReDim dizi(1 To UBound(var, 1), 1 To UBound(var, 2))
' Variant dizisindeki değerleri String dizisine aktarma
For i = 1 To UBound(var, 1)
For j = 1 To UBound(var, 2)
dizi(i, j) = var(i, j)
Next j
Next i
' Dizinin üçüncü satır, ikinci sütun elemanını mesaj kutusunda gösterme
MsgBox dizi(3, 2)
End Sub
Şimdi bunu bir de çok boyutlu dizide yapalım
Çok kolonlu bir Variant diziyi tek boyutlu ve çok boyutlu standart dizilere atama işlemleri aşağıdaki gibidir.
Sub çokkolonlu_variant_to_dizi()
Dim var As Variant
Dim dizi() As String
Dim s As Integer, k As Integer, i As Integer
var = Range("B2:D10").Value2
ReDim dizi(1 To UBound(var, 1) * UBound(var, 2))
s = 0
For k = 1 To UBound(var, 2)
For i = 1 To UBound(var, 1)
dizi(i + s) = var(i, k)
Next i
s = s + UBound(var, 1) 'her boyut geçişinde tekrar başa dönmesin diye aratoplam alınır
Next k
MsgBox dizi(15)
End Sub
Aynı diziyi iki boyutlu bir standart diziye atamak da aşağıdaki gibi olur.
Sub çokkolonlu_variant_to_çokboyutludizi()
Dim var As Variant
Dim dizi() As String
Dim s As Integer, k As Integer, i As Integer
var = Range("B2:D10").Value2
ReDim dizi(1 To UBound(var, 1), 1 To UBound(var, 2))
For k = 1 To UBound(var, 2)
For i = 1 To UBound(var, 1)
dizi(i, k) = var(i, k)
Next i
Next k
MsgBox dizi(2, 3)
End Sub
Dizileri Bir Prosedüre Parametre Olarak Gönderme
Dizileri bir Sub veya Function prosedüre parametre olarak gönderebiliriz. Unutulmaması gereken nokta, diziler her zaman ByRef anahtar kelimesi ile gönderilir. Aşağıda basit bir örneğimiz bulunuyor.
Sub dizigonder()
Dim d(5) As String
'sadece iki elemanı dolduralım
d(0) = "volkan"
d(1) = "meltem"
Call mesajver(d)
End Sub
Sub mesajver(ByRef dz() As String)
For Each a In dz
If a <> "" Then i = i + 1
Next a
eleman = UBound(dz) - LBound(dz) + 1
MsgBox eleman & " adet elemanın " & i & " tanesi doludur"
End Sub
Prosedürden Sonuç Olarak Dizi Döndürme
Fonksiyonlar konusunda göreceğimiz gibi, genelde bir fonksiyondan sadece tek bir değer döndürülür. Aslında bu birçok yerde doğru diye anlatılan yanlış bir bilgidir. Genelde durum böyledir ancak fonksiyonlar tek bir değer döndürmek zorunda değildir, sadece dönüş değeri tek olmak zorundadır. Zira gerek ByRef kullanımı, gerek dönüş değerinin dizi/collection olması sayesinde fonksiyonlar pekala çoklu değer döndürebilirler. Aşağıdaki örnekte çoklu değer olarak dizi döndürüyoruz.
Sub diziata()
'boyutsuz dizi yaratılır
Dim d() As Integer
d = dizidondur '6 değerli bir dizi döner, istediğimizi kullanırız
Debug.Print d(3)
End Sub
Function dizidondur() As Integer() 'dönen değer tipi () ile biter ki dizi döndürdüğü anlaşılsın
Dim dizim(0 To 5) As Integer
For x = 0 To 5
dizim(x) = 10 * x
Next x
dizidondur = dizim
End Function
Split ve Join
Split
Dizilerle kullandığımız çok faydalı iki fonksiyon vardır.
Bunlardan ilki Split fonksiyonudur. Bununla, belirli bir ayraçla birleşmiş olan bir String ifade parçalara ayrılarak bir Variant içine veya klasik bir dizi içine aktarılır. Gördüğünüz gibi bu da aslında bir nevi dizi tanımlama yöntemi olmaktadır.
Bu fonksiyon, genelde bir hücreden ";" veya "," ile ayrılmış elemanları tek tek elde etmek için kullanılır.
Sub splitornek()
Dim s As String
s = "35516;37770;34234"
Dim dizi As Variant 'veya Dim dizi() as String şeklinde boyutsuz dizi
dizi = Split(s, ";")
Debug.Print dizi(0) '35516 döner
End Sub
Bu Split fonksiyonunu kullanarak bir UDF yazmıştım, ki bu sayfayı yazarkenki en yüksek Excel sürümü olan Excel 2016 içinde bile bu fonksiyon hala yerel olarak bulunmuyor. Bunu anlamak gerçekten zor, zira çok ihtiyaç duyulan bir fonksiyon olduğunu düşünüyorum. Neyseki VBA ve UDF var da başımızın çaresine bakabiliriyoruz.
Bu fonksiyon, bir metindeki belirtilen indeksteki kelimeyi seçiyor. Ayracı default olarak " " yani boşluk belirledim, ancak isteyen farklı bir ayraç da belirleyebiliyor. İşte bu fonksiyonum:
Function kelimesec(hucre As Range, kaçıncı As Byte, Optional ayrac As String = " ")
'normal bir cümlede ayrac boşluk olacağı için ayracı girmeye gerek yok, zaten default olarak " " atadım.
'ama içeriği mesela / ile ayrılmış bir hücre varsa 3.parametreyi / olarak girersiniz
Dim kelimeler As Variant
kelimeler = Split(hucre.Value2, ayrac)
kelimesec = kelimeler(kaçıncı - 1)
End Function
Join
Diğer faydalı fonksiyon da Join olup bir dizi içindeki elemanları, belirtilen ayraçla birleştirip bir String ifade döndürür, yani Splitin tersi gibi çalışır.
Sub birkolonlu_variant_to_dizi()
Dim var As Variant
Dim dizi() As String
Dim kombine As String
Dim i As Integer
var = Range("B2:B10").Value2
ReDim dizi(1 To UBound(var, 1))
For i = 1 To UBound(var, 1)
dizi(i) = var(i, 1)
Next i
kombine = Join(dizi, ", ")
MsgBox kombine
End Sub
2 Kolonlularda
Sub çokkolonlu_variant_to_dizi()
Dim var As Variant
Dim dizi() As String
Dim kombine As String
Dim s As Integer, k As Integer, i As Integer
var = Range("B2:D10").Value2
ReDim dizi(1 To UBound(var, 1) * UBound(var, 2))
s = 0
For k = 1 To UBound(var, 2)
For i = 1 To UBound(var, 1)
dizi(i + s) = var(i, k)
Next i
s = s + UBound(var, 1) 'her boyut geçişinde tekrar başa dönmesin diye aratoplam alınır
Next k
kombine = Join(dizi, ", ")
MsgBox kombine
End Sub
2. Yöntem: Transpoze (Sadece Tek Kolonsa)
kombine = Join(WorksheetFunction.Transpose(Range("B2:B10").Value), ", ")
Dizilerle ilgili sık yapılan işlemlerden biri de, bir elemanın o dizide bulunup bulunmadığı ve/veya bulunuyorsa da index numarasını öğrenmek olacaktır.
Filtreleme
Bu işlemlere geçmeden önce Filter fonksiyonundan bahsedelim. Bu metod ile String tipli bir dizinin içinde belirli bir metni arar ve bunları filtreler, eşleşmeyenleri hariç tutarız. (Burada filtrelemekten kastım, elemek değil seçmektir.)
Syntax: Filter(dizi, aranan, [Dahilmihariçmi], [aramatipi])
dizi olarak tek boyutlu bir diziyi parametre olarak veririz, bu nedenle range atanmış bir Variant dizi parametre olarak verilemez.
Dahilmihariçmi parametresi girilmez veya True girilirse aranan kelimeyi içeren kelimeler aranır, False girilirse aranan kelimeyi içermeyen elemanlar filtrelenir.
aramatipi girilmez veya vbBinaryCompare girilirse küçük büyük harf duyarlılığı gözetilerek arama yapılır, vbTextCompare girilirse küçük büyük harf duyarlılığı olmadan arama yapılır. İstisna: ş ve Ş, ç ve Ç v.s aynı kabul edilirken bir tek İ ve i farklı algılanır.
Bununla beraber bu metodu, aranan metnin tam eşleşme durumu için kullanamazsınız. Yani "is" metnini aradığınızda "is, istanbul, mistik" kelimelerinin tamamı gelir.
Örnek 1: Basit Filtreleme
Sub basitfiltre()
Dim isimler As Variant
Dim filtreli As Variant
isimler = Array("volkan", "erhan", "hakan", "özkan", "meltem", "serkan")
filtreli = Filter(isimler, "kan", True, vbTextCompare)
For Each f In filtreli
Debug.Print f
Next f
End Sub
Örnek 2: İki Boyutlu Dizide Filtreleme
Aşağıdaki örnekte ise içinde "ankara" geçen tüm kelimeler aranır ve filtrelenir. Ancak bu sefer kaynak dizimiz 2 boyutlu ve önce onu tek boyutlu diziye çevirmemiz gerekiyor.
Sub ikiboyutlu_filtre()
Dim var As Variant
Dim tekboyutlu() As String
Dim filtreli As Variant
Dim i As Integer, j As Integer, k As Integer
var = Range("A1:C10").Value2
ReDim tekboyutlu(1 To UBound(var, 1) * UBound(var, 2))
k = 1
For i = 1 To UBound(var, 1)
For j = 1 To UBound(var, 2)
tekboyutlu(k) = var(i, j)
k = k + 1
Next j
Next i
filtreli = Filter(tekboyutlu, "ankara", True, vbTextCompare)
For Each f In filtreli
Debug.Print f
Next f
End Sub
Dizilerle ilgili sık yapılan işlemlerden biri de, bir elemanın o dizide bulunup bulunmadığı ve/veya bulunuyorsa da index numarasını öğrenmek olacaktır.
Filtreleme
Bu işlemlere geçmeden önce Filter fonksiyonundan bahsedelim. Bu metod ile String tipli bir dizinin içinde belirli bir metni arar ve bunları filtreler, eşleşmeyenleri hariç tutarız. (Burada filtrelemekten kastım, elemek değil seçmektir.)
Syntax: Filter(dizi, aranan, [Dahilmihariçmi], [aramatipi])
dizi olarak tek boyutlu bir diziyi parametre olarak veririz, bu nedenle range atanmış bir Variant dizi parametre olarak verilemez.
Dahilmihariçmi parametresi girilmez veya True girilirse aranan kelimeyi içeren kelimeler aranır, False girilirse aranan kelimeyi içermeyen elemanlar filtrelenir.
aramatipi girilmez veya vbBinaryCompare girilirse küçük büyük harf duyarlılığı gözetilerek arama yapılır, vbTextCompare girilirse küçük büyük harf duyarlılığı olmadan arama yapılır. İstisna: ş ve Ş, ç ve Ç v.s aynı kabul edilirken bir tek İ ve i farklı algılanır.
Bununla beraber bu metodu, aranan metnin tam eşleşme durumu için kullanamazsınız. Yani "is" metnini aradığınızda "is, istanbul, mistik" kelimelerinin tamamı gelir.
Örnek 1: Basit Filtreleme
Sub filtreornek()
Dim şubeler As Variant
Dim filtreli As Variant
Dim geçici() As String
Dim i As Integer
'çeşitli şubelerin olduğu alandan okuma yapılır (sırayla ankara, antalya, ankara, istanbul, sivas yazsın)
şubeler = Range("A1:A5").Value
'Variant ve 2 boyutlu olan şubeler dizisi klasik bir boyutlu bir diziye atanır
ReDim geçici(1 To UBound(şubeler, 1))
For i = 1 To UBound(şubeler, 1)
geçici(i) = şubeler(i, 1)
Next i
filtreli = Filter(geçici, "ankara", True, vbTextCompare)
Set Hedef = Range("C1").Resize(UBound(filtreli) + 1, 1)
Hedef.Value = filtreli
End Sub
Bir Metin Bir Dizide Var mı Kontrolü?
Filter fonksiyonunu öğrendiğimize göre şimdi bunu daha spesifik bir amaçla kullanabiliriz: Bir metnin bir dizide olup olmadığını öğrenmek için. Bunun için birkaç yöntem bulunuyor.
Yöntem 1: Filter Kullanarak
İlk yöntemde diziye filtre uygular ve elde ettiğimiz yeni dizinin üst limitine bakarız. Eğer üst limit 0 veya daha üstü bir sayı ise aradığımız metin dizide var demektir, -1 ise dizide bulunmuyor demektir.
Sub varmı1sub()
'1.Yöntem: Filter, tam eşleşme aranmıyor
Dim şubeler As Variant
Dim filtreli As Variant
Dim geçici() As String
Dim i As Integer
'çeşitli şubelerin olduğu alandan okuma yapılır (sırayla ankara, antalya, ankara, istanbul, sivas yazsın)
şubeler = Range("A1:A5").Value
'Variant ve 2 boyutlu olan şubeler dizisi klasik bir boyutlu bir diziye atanır
ReDim geçici(1 To UBound(şubeler, 1))
For i = 1 To UBound(şubeler, 1)
geçici(i) = şubeler(i, 1)
Next i
filtreli = Filter(geçici, "ankara", True, vbTextCompare)
If UBound(filtreli) = -1 Then
MsgBox "aradığınız kelime dizide yok"
Else
MsgBox "aradığınız kelime dizide var"
End If
End Sub
Yöntem 2: Fonksiyon Haline Getirme
Bunu bir fonksiyon haline getirmek daha kullanışlı yapacaktır.
'1'in function hali
Function Varmı1(aranan As String, dizi As Variant) As Boolean
Varmı1 = (UBound(Filter(dizi, aranan, True, vbTextCompare)) > -1)
End Function
'--------
Sub test1()
isimler = Array("volkan", "erhan", "hakan", "özkan", "meltem", "serkan")
Debug.Print Varmı1("volk", isimler) 'True
Debug.Print Varmı1("volkan", isimler) 'True
End Sub
Eğer tam eşleşme istiyorsak, Match fonksiyonunu kullanmalıyız veya Filter dışında çözüm sağlayan 2. yöntemi denemeliyiz. Bu yöntemde 1. yöntemin aksine sayısal değerleri de arayabiliyoruz. Bu yüzden hem sayıları hem stringleri kapsayacak şekilde Variant tipte bir aranan parametresi belirtiyoruz.
Yöntem 2: Match Fonksiyonu Kullanarak
Function Varmı2(aranan As Variant, dizi As Variant) As Boolean
'2.yöntem) Match, tam eşleşme sağlanıyor
On Error GoTo hata
Varmı2 = Not IsError(WorksheetFunction.Match(aranan, dizi, 0))
Exit Function
hata:
Varmı2 = False
End Function
'-----------
Sub test2()
isimler = Array("volkan", "erhan", "hakan", "özkan", "meltem", "serkan")
Debug.Print Varmı2("volk", isimler) 'False
Debug.Print Varmı2("volkan", isimler) 'True
sayılar = Array(1, 2, 3)
Debug.Print Varmı2(1, sayılar) 'True
Debug.Print Varmı2(10, sayılar) 'False
End Sub
Yöntem 3: Join ve InStr Fonksiyonları Kullanarak
3. yöntem, dizi elemanlarını Join ile birleştirip, yine bir String fonksiyonu olan ve bir string içinde bir alt metin arayan InStr fonksiyonu ile arama yapmak şeklinde olabilir, üstelik bu yöntem tam eşleşme sağlar. Bunda da 2. yöntemde olduğu gibi sayısal değerleri de arayabiliyoruz, o yüzden fonksiyonumuzu Variant tanımlıyoruz.
Function varmı3(aranan As Variant, dizi As Variant) As Boolean
ayraç = "-"
joinli = ayraç & Join(dizi, ayraç) 'tam eşleşmeyi sağlamak için başına da ilgili ayracı koyuyoruz
If InStr(1, joinli, ayraç & aranan & ayraç, vbTextCompare) Then
varmı3 = True
Else
varmı3 = False
End If
End Function
Sub test3()
isimler = Array("volkan", "erhan", "hakan", "özkan", "meltem", "serkan")
Debug.Print varmı3("volk", isimler) 'False
Debug.Print varmı3("volkan", isimler) 'True
sayılar = Array(1, 2, 3)
Debug.Print varmı3(1, sayılar) 'True
Debug.Print varmı3(10, sayılar) 'False
End Sub
Yöntem 4: Dictionary Kullanarak
Daha basit bir yol olan Dictionary kullanılabilir, tabi kodun genel yapısı buna uygun ise. Bunu sonraki sayfalarda göreceğiz.
Yöntem 5: Arama Algoritmaları Kullanarak
Arama algoritmalarından birini kullanarak kendi fonksiyonunuzu yazmayı önerebilirim ama çok profesyonelleşmek istemediğiniz sürece bu sulara girmenize gerek yoktur.
Var mı, varsa kaçta?
Yukarıdaki Match fonksiyonunu kullandığımız 2. yöntemde aslında ilgili metnin kaçıncı sırada olduğunu buldurmaya yarayan bir fonksiyon kullanmış olduk. Eğer bulamazsa hata döndürür, zaten biz de hata döndürüp döndürmediğine bakıyorduk, döndürmüyorsa elemanı içeriyor diyorduk. Peki ya hata döndürmüyorsa? Döndürdüğü şey sıra numarası olmaktadır. Dizileri sıralama Algoritma kavramına detaylı girdiğimizde karşımıza çıkan en temel algoritmalardan biri de sıralama algoritmalarıdır.(Diğeri de hemen az önce bahsettiğim arama algoritmasıdır). Bu sıralama algoritmalarını anlamak önemli, zira dizilerle ilgili olarak sıralama yapmayı sağlayan ne yerleşik bir fonksiyon/metod, ne de arama yaparken kullandığımız gibi yardımcı yöntemler(Match, Join) bulunmaktadır. Kendi fonksiyonumuz kendimizin yazması gerekmektedir. Bunların da kendi içinde türleri vardır. Bubble Sort, Merge Sort, Quick Sort gibi. Burada bunlardan ikisine bakacağız. Şu ve şu sitelerde daha bol miktarda örnek bulunmakta, şu ve şu(Türkçe) sitede ise muazzam bir kaynak bulunmaktadır. Bu son sitelerden farklı yönteleri öğrenip VBA kodlamasını yapmayı deneyebilirsiniz. Bubble Sort Bu yöntem en basit yöntem olup, aynı zamanda en yavaş çalışan yöntemdir de. Gerçi küçük dizilerde bu yavaşlık problemi çok farkedilmez. Çalışma yöntemi, tekrarlı bir şeklide komşu değerlerin sıralamasını yapmaktan ibarettir. Aşağıdaki örnekte tüm aşamaların üzerinden tek tek geçeceğiz. Elimizdeki dizi 5,1,4,2,8 rakamlarından oluşsun.
1. Aşama
Önce | Sonra |
---|---|
( 5 1 4 2 8 ) | ( 1 5 4 2 8 ) |
( 1 5 4 2 8 ) | ( 1 4 5 2 8 ) |
( 1 4 5 2 8 ) | ( 1 4 2 5 8 ) |
( 1 4 2 5 8 ) | ( 1 4 2 5 8 ) 'burada bir değişiklik olmaz, sırası doğru |
2. Aşama
Önce | Sonra |
---|---|
( 1 4 2 5 8 ) | ( 1 4 2 5 8 ) |
( 1 4 2 5 8 ) | ( 1 2 4 5 8 ) |
( 1 2 4 5 8 ) | ( 1 2 4 5 8 ) |
( 1 2 4 5 8 ) | ( 1 2 4 5 8 ) |
Şu anda sıralama tamam ancak bu aşamada 1 tane de olsa bir değişim olduğu için algoritmanın devam etmesi lazım, ta ki hiç değişim olmayana kadar.
3. Aşama
Önce | Sonra |
---|---|
( 1 2 4 5 8 ) | ( 1 2 4 5 8 ) |
( 1 2 4 5 8 ) | ( 1 2 4 5 8 ) |
( 1 2 4 5 8 ) | ( 1 2 4 5 8 ) |
( 1 2 4 5 8 ) | ( 1 2 4 5 8 ) |
Evet 3.aşamada artık bir değişiklik yok ve algoritmamız durur. Şimdi bunun kodunu görelim.
Function BubbleSort(arr) As Variant
Dim geçici As Variant
Dim i As Long
Dim j As Long
Dim lngMin As Long
Dim lngMax As Long
lngMin = LBound(arr)
lngMax = UBound(arr)
For i = lngMin To lngMax - 1
For j = i + 1 To lngMax
If arr(i) > arr(j) Then
geçici = arr(i)
arr(i) = arr(j)
arr(j) = geçici
End If
Next j
Next i
BubbleSort = arr
End Function
'-------
Sub bubble_sort_test()
Dim dizi As Variant
Dim sıralı As Variant
dizi = Array(5, 1, 4, 2, 8)
sıralı = BubbleSort(dizi)
Call Diziyazdır(sıralı)
End Sub
QuickSort Algoritması
QuickSort (Hızlı sıralama) algoritması, böl ve fethet felsefesine dayanan bir yöntem uygular. Bu yöntemde, bir referans seçilir ve bu referanstan küçük olanlar sol tarafa, büyük olanlar sağ tarafa konulur. İşte bu algoritmanın VBA kodu:
Function Parçala(ByRef dizi As Variant, ByVal low As Integer, ByVal high As Integer) As Integer
referans = dizi(high)
Debug.Print "Referansımız:" & referans & ".Bundan küçükler sola, büyükler sağa. referans ortalarına. Soldakiler önce ele alınacak, sağdakiler daha sonra."
i = low - 1
For J = low To high - 1
If dizi(J) <= referans Then
i = i + 1
Debug.Print i, J, dizi(J) & "<= referans olduğu için " & dizi(i) & " ile " & dizi(J) & " yer değiştirecek." & dizi(i) & " yavaşça sağa kayacak: " & Join(dizi, "-")
geçici = dizi(i)
dizi(i) = dizi(J)
dizi(J) = geçici
Else
Debug.Print i, J, dizi(J) & ">" & "referans(" & referans & ") olduğu için yer değişme yok"
End If
Next J
Call DiziBitişikyazdır(dizi, "for çıkışında dizinin durumu:")
Debug.Print dizi(i + 1) & " ile " & dizi(high) & " yer değiştirecek, yani referans ortaya girecek."
geçici2 = dizi(i + 1)
dizi(i + 1) = dizi(high)
dizi(high) = geçici2
Call DiziBitişikyazdır(dizi, "Bu aşamanın sonunda dizimiz:")
Debug.Print "-----------------------------------------" & vbNewLine
Parçala = i + 1
End Function
'------------------------------------
Sub hızlısırala(ByRef dizi As Variant, ByVal low As Integer, ByVal high As Integer)
If low < high Then
Pi = Parçala(dizi, low, high)
hızlısırala dizi, low, Pi - 1
hızlısırala dizi, Pi + 1, high
End If
End Sub
'------------------------------------
'bu kodu çalıştırarak test ediyoruz
Sub quick_sort_test()
karışıkdizi = Array(5, 1, 4, 2, 8)
'karışıkdizi = Array(52, 3, 45, 32, 8, 19, 47, 26, 1, 100, 68, 24, 12, 9, 72, 55, 36)
Call DiziBitişikyazdır(karışıkdizi, "İlk giriş:")
hızlısırala karışıkdizi, 0, UBound(karışıkdizi)
Call DiziBitişikyazdır(karışıkdizi, "Nihai sıralama sonucu:")
End Sub
Diziler için Utility Modülü
Dizilerle ilgili olarak sık yapılan işlemleri, özellikle test/kontrol için yapılan işlemleri bir modül içinde toparlamak iyi bir fikir olacaktır. Bunlardan sıralamayı yukarıda görmüştük, diğerlerine birkaç örneği ise aşağıda bulabilirsiniz.
Sub Diziyazdır(dizi As Variant)
For Each d In dizi
Debug.Print d
Next d
End Sub
'------------------
Sub DiziBitişikyazdır(dizi As Variant, Optional başlık As String = "Birleşen dizi:")
bitişik = Join(dizi, "-")
Debug.Print başlık & bitişik & vbNewLine
End Sub
'-------------
Function ikidizibirleştir(dizi1 As Variant, dizi2 As Variant)
Dim geçici As Variant
adet = UBound(dizi1) + UBound(dizi2) + 2
ReDim geçici(adet - 1)
For i = LBound(dizi1) To UBound(dizi1)
geçici(i) = dizi1(i)
Next i
For i = UBound(dizi1) + 1 To adet - 1
geçici(i) = dizi2(i - 1 - UBound(dizi1))
Next i
'kontrol için
DiziBitişikyazdır geçici
ikidizibirleştir = geçici
End Function
Hatırlatma: Sayfanın başında belirttiğim gibi buraya da bakmanızı tavsiye ediyorum.