Dictionary
Dizilerden sonra en çok kullanılan toplu değer saklama aracı Dictionary'lerdir. Collectionlara benzerler, onlar gibi değerleri Key/Value ikilisi şeklinde tutarlar. Gerçi Collectionlarda ikili yapı yerine tekli yapı kullanımı daha yaygındır; ikili yapı gerektiğinde çoğunlukla Dictionary kullanılır. Bu kadar çok benzerlik gösterdikleri için bu sayfa boyunca yeri geldikçe Collectionlarla olan benzerlik ve farklılıklara değinilecek, en aşağıda da genel bir karşılaştırma yapılacaktır.
Ne, nasıl, nerede
Adı üzerinde, bu yapıları bir sözlük gibi kullanırız. Mesela çeşitli kategorideki kelimeler için Türkçe-İngilizce karşılıkları şeklinde bir liste oluşturabiliriz, veya bölge kodu-bölge adı gibi lookup listeleri oluşturabiliriz.
Key, Item
---- ----
bir, one
iki, two
.......
veya
5001, Akdeniz
5002, Başkent
.....
Diğer örnekleri şöyle sıralayabiliriz:
- Key:Ürün kodu, Item:ürün açıklaması(ürün yerine şube, bölge, stok v.s birçok şey konulabilir)
- Key:Ürün kodu, Item:ürün alış/satış fiyatı
- Key:Ürün kodu, Item:ürün cirosu/stok adedi v.s
- Key:Müşteri kodu, Item:Telefon no
- Key:Kısaltma, Item:Kısaltmanın uzun/açık hali(CHP, Cumhuriyet Halk Partisi gibi)
Tanımlama şekilleri
Collectionlardan farklı olarak Dictionaryleri kullanmak için projemize Scripting.Runtime kütüphanesini (Library) eklemek gerekir. (Tools>References>Microsoft Scripting Runtime)
Dictionaryler, birçok object türü gibi hem Late hem Early binding şeklinde tanımlanabilirler. (Early ve Late binding hakkında detay bilgi ve birbirlerine göre avantaj/dezavantajları için buraya tıklayınız)
'Early binding olarak tanımlanırsa
Dim sayılar As New Scripting.Dictionary
'veya
Dim sayılar As Scripting.Dictionary
Set sayılar = New Scripting.Dictionary
'Late binding olarak tanımlanırsa
Dim sayılar As Object
Set sayılar = CreateObject("Scripting.Dictionary")
Early Binding'in tek satır ve iki satır versiyonu arasındaki farkı Collectionlarda anlattığımız için burada tekrar aynı detaya girmiyorum.
Property ve Metodları
Eleman ekleme
Collectionlarda olduğu gibi eleman eklemek için Add metodu kullanılır. Dictionarylerde zorunlu olan iki parametre vardır, bunlar sırasıyla şöyledir: Key ve Item. (Collectionlarda Key opsiyoneldir)
Bunların ikisi de Variant tiptedirler, yani her değeri taşıyabilirler, buna Dictionary dahil, yani Dictionary Dictionary'si diye bir kavram teknik olarak mümkündür ve aşağıda da bir örneğini yapıyor olacağız. (Collectionlarda Key'ler string olmak zorundaydı)
Şimdi iki basit örnek yapalım, ilki sayıların Türkçe-İngilizce karşılığı, ikincisi de lookup liste olarak, bölge kodları ve bölge adları olsun.
Dim sayılar As New Scripting.Dictionary
sayılar.Add "bir", "one"
sayılar.Add "iki", "two"
Debug.Print sayılar("bir") 'one yazar
İkinci örneğimiz de şöyle:
Dim bölgeler As New Scripting.Dictionary
bölgeler.Add 5001, "Akdeniz"
bölgeler.Add 5002, "Başkent"
Debug.Print bölgeler(5001)
Key parametresi Collectionlarda olduğu gibi benzersiz bir parametre olmalıdır, yani sadece bir kere kullanılmalıdır. Collectionlarda vurgu Item'dadır, yani sözel ifade etmek gerekirse biz Item'ı depolarız ve ona istenirse Key adında bir isim veririz. Dictionarylerde ise vurgu Key'dedir, Key ile ona karşılık gelen yani onun lookup'ı olan Item birlikte depolanır.
Key de Item de Variant tipli parametrelerdir demiştik ancak Key'in bir istisnası var, dizi(array) olamaz. Item ise array dahil herşey olabilir.
Item propertysi/özelliği
Collectionlardan farklı olarak Dictionarylerde eleman eklemenin farklı bir yolu daha vardır: Doğrudan atama yöntemi (Implicit adding). Eğer Dictionary içinde olmayan bir anahtara atama yapılmaya çalışılırsa onu doğrudan eklemiş oluruz. Bunun için Item property'sini kullanırız, veya bu property düşürülerek de yazılabilir.
'madenler isimli dict içinde şuan sadece altın ve gümüş var olsun
madenler.Item("diamond")="elmas" 'madenler.Add "diamond", "elmas" ile aynı
Bu özellik, parametre olarak Key'i alır. Yani Key'i "diamond", Item'ı "elmas"tır. Mesela bu elemanda Item'ın baş harfini büyük olarak değiştirmek için şu kodu yazarız.
madenler.Item("diamond")="Elmas"
'veya kısaca
madenler("diamond")="Elmas"
ÖNEMLİ: Collectionlardaki aynı mantıkla, elemanlara ters yönden erişim mümkün değildir. Yani nasıl ki collectionlarda Key'ler uniqe'tir ve Item belirtilerek Key'e ulaşamıyorduk, dictionarylerde de Item belirterek Key'lere ulaşamayız.
Exists metodu ile "Varmı" kontrolü
Bir Key'in Dictionary'de olup olmadığını Exists metodu ile kontrol ederiz. Varsa True, yoksa False döndürür. Genel kullanımı "Eleman yoksa onu ekle" şeklindedir.
If Not dict.Exists("elma") Then dict.Add "elma", "apple"
Daha sadece olarak şöyle de diyebilirdik. Zira bu yöntemle, dictionary içinde "olmayan" bir elemanı doğrudan dictionary'ye ekliyorduk.
dict("elma")= "apple"
Ama "Varmı" kontrolünü yaptığımız sırada başka işlemler de yapmamız gerekirse Exists kullanmalıyız.
Collectionlarda doğrudan böyle bir metod yoktur. Bu işlem, hata kontrolü ile dolaylı olarak yapılmaktadır.
Items metodu
Bu metod, Dictionary içindeki tüm itemları döndürür ve bunu 0 tabanlı bir dizi olarak depolar.
meyveler=dict.Items 'elemanları diziye atadık
Debug.Print Join(meyveler,"-") 'dizideki elemanları - ile birleştirdik
Döngüyle tamamına erişebiliriz.
For Each i In dict.Items
Debug.Print i
Next i
Herhangi bir indeksteki Item'a ulaşmak için de kullanılır.
Debug.Print dict.Items(0)
Key propertysi
Dictionary içindeki belli bir Key'in değerini değiştirmek için kullanılır. (Key'in karşılık geldiği Item'ı değil. Bunun için Item özelliğini kullanıyoruz)
dict.Add "elma", "apple"
dict.Key("elma") = "Elma"
Bu yukardaki gibi bir örnekteki tekil bir elemanı değiştirmekten ziyade tüm Key'lerin önüne sabit bir ifade eklemek gibi çoklu değişiklikler yapılması daha olasıdır, ki bunu da döngü ile yaparız.
For Each k In dict.Keys
dict.Key(k) = "M_" & k 'tüm meyvererin önünde Meyvenin M'si ve _ kodyduk
Next k
DIKKAT: Bu özellik Write-Only olup sadece Key'in değerini değiştirmek için kullanılır, Item'ı elde etmek için kullanılmaz. Item'ı elde etmek için nasıl kullanacağımıza elemanlara erişim bölümünden bakabilirsiniz.
Keys metodu
Dictionary içindeki tüm Keyleri döndürür ve bunu 0 tabanlı bir dizi olarak depolar.
meyveler=dict.Keys
Debug.Print Join(meyveler,"-")
Herhangi bir indeksteki Key'e ulaşmak için de kullanılır.
Debug.Print dict.Keys(0) 'kısaca dict(0) da yazılabilir
Remove ve RemoveAll ile eleman çıkarma
Belirtilen key'deki elemanı çıkarmak için Remove, tüm elamanları çıkarmak için yani Dictionary'yi boşaltmak için RemoveAll metodunu kullanırız.
meyveler.Remove "elma"
meyveler.RemoveAll
Collectionlarda RemoveAll yoktu, bunun yerine tüm Collection içinde dolaşıp elemanları tek tek kaldırmak gerekiyordu veya yeni Collection atama veya Nothing ataması yapmak gibi dolaylı yollara başvuruluyordu.
NOT: Nothing ataması veya New Dictionary ataması da ilgili dictionary'nin içini boşaltır.
Set meyveler = Nothing
Set meyveler = New Dictionary
RemoveAll ve New Dictionary arasındaki ayrımı görmek için şu sayfada Pivot Tablolarla ilgili kısımda "Birden çok fieldda filtre uygulama" başlığı altındaki örneği inceleyin.
CompareMode ile küçük/büyük harf duyarlılığı
Dictionaryler default olarak küçük/büyük harf ayrımına duyarlıdır (case-sensitive'dir), yani "elma" ve "Elma" farklı olarak algılanır. Bu duyarlılığı kaldırmak için aşağıdaki kod yazılır.
Dim dict As New Dictionary
dict.CompareMode = vbTextCompare 'veya numerik değer olarak 1
'tekrar case sensitive yapmak için şöyle yazılır
dict.CompareMode = vbBinaryCompare 'veya 0
Count ile eleman saymak
Collectiondaki gibi içerdeki toplam eleman sayısını verir.
Debug.Print dict.Count
Elemenlara tekil erişim ve Dictionary içinde dolaşma
Itemlara erişim ile Key'lere erişim bazen kafa karışıtırıcı olabilmektedir. Bu kısımda bunların detaylarına değinmeye çalışacağım.
Collectionlarda olduğunun aksine Dictionary'lere doğrudan Index numarası ile ulaşılmaz, zira Index diye bir şey yoktur. Hatırlayacak olursak Collectionlar sıralı bir yapıya sahipken Dictionary'lerde sıra yoktur. Sonuç olarak, Dictionary'lerde elemanlara erişim onun Key'i aracılığı ile olur, bu da ya Indexli Key belirterek ya da doğrudan Key'in kendisi (Stringse) yazılarak olmaktadır. Indexli Key ile ulaştığımız şey Key'in kendisi iken, Key'in kendisini yazarak eriştiğimiz şey ise bu Key'in lookup değeridir. Collectionlarda Index ile Item'a ulaşıyorduk, ki buna Key adı verilerek de ulaşılabilir demiştik, Dictionarylerde ise Indexli key vererek Item'a ulaşıyoruz.
DİKKAT: Key ismini Key propertysi ile kullanamayız. Zira bu property write-only'dir, yani sadece Key'in değerini değiştirmek için kullanılır.
Farkındayım, bütün bunlar şu an çok karışık geliyor olabilir. Aşağıdaki örnekler biraz daha aydınlanmanıza yarayacaktır. Biraz aşağıda tüm bunları derleyip toplayan bir örnek ve bir tablo daha göreceksiniz. Ondan sonra kendi örneklerinizi de yapınca konu iyice pekişecektir.
Dim dict As New Dictionary
dict.Add "elma", "apple"
'Tekil elemana read erişimi
Debug.Print dict("elma") 'Key'in kendisi ile Itema erişim. apple değerini verir
Debug.Print dict.Keys(0) 'Indeksli Key ile Key'e erişim. elma değerini verir
Debug.Print dict.Key("elma") 'Hata verir. Çünkü Key propertysi write-onlydir.
'Tekil elemanlara write erişimi
dict.Key("elma")="Elma" 'Key'in değeri değişti, onun lookupı olan Itemın değil. Yani elme Elme oldu.
dict("elma")="apple" 'Itemın değerini değişti. Eleman yoksa Implicit ekleme olur. Yani elma, apple ikilisi eklenir
dict.Item("apple")="Apple" 'Item'ın değerini değiştirdik.
Aşağıda ise erişim yöntemleriyle ilgili küçük bir collection/dictionary karşılaştırması bulunuyor.
Dim col As New Collection
Dim dict As New Scripting.Dictionary
'Collection örneği
col.Add "Elma" '1.index
col.Add "Armut" '2.index
col.Add "Erik" '3.index
Debug.Print col(2) 'veya col.Item(2). Armut yazar
'Key'li Coll örneği. ilk yazılan Item, ikincisi Key'dir
col.Add "Elma", "Apple"
col.Add "Armut", "Pear"
col.Add "Erik", "Plum"
Debug.Print col("Plum") 'Erik yazar
Debug.Print col("Erik") 'Hata verir. Erişim Item'la olmaz,
'Dictionary örneği. ilk yazılan Key, ikincisi Item'dır
dict.Add "Elma", "Apple"
dict.Add "Armut", "Pear"
dict.Add "Erik", "Plum"
Tüm elemanları dolaşma
Dictionary'lerin tüm elemanları dolaşmak için tüm dizimsi yapılarda olduğu gibi For Next döngülerini kullanıyoruz.
For Each döngüsünü hem Early Binding hem Late Binding için kullanabilirken, For Next döngüsünü sadece Early Binding tanımlama yapıldığında kullanabiliriz. (Binding çeşitleri hakkında bilgi için tıklayınız)
Dim k As Variant
For Each k In dict.Keys
Debug.Print k, dict(k)
Next k
For Each k In dict.Keys satırını For Each k In dict şeklinde de yazabilirdik. Yani sadece Dictionary'nin adını yazmak onun Keys propertysine bakacağız diye algılanır. Collectionlarda böyle bir yazımla Items kastedilir.
Klasik For döngüsünde ise dikkat edilecek husus, başlangıcın 0'dan başlaması, bitiş indeksinin de eleman sayısı - 1 olmasıdır.
Elemenlara tekil erişim ve Dictionary içinde dolaşma
Itemlara erişim ile Key'lere erişim bazen kafa karışıtırıcı olabilmektedir. Bu kısımda bunların detaylarına değinmeye çalışacağım.
Collectionlarda olduğunun aksine Dictionary'lere doğrudan Index numarası ile ulaşılmaz, zira Index diye bir şey yoktur. Hatırlayacak olursak Collectionlar sıralı bir yapıya sahipken Dictionary'lerde sıra yoktur. Sonuç olarak, Dictionary'lerde elemanlara erişim onun Key'i aracılığı ile olur, bu da ya Indexli Key belirterek ya da doğrudan Key'in kendisi (Stringse) yazılarak olmaktadır. Indexli Key ile ulaştığımız şey Key'in kendisi iken, Key'in kendisini yazarak eriştiğimiz şey ise bu Key'in lookup değeridir. Collectionlarda Index ile Item'a ulaşıyorduk, ki buna Key adı verilerek de ulaşılabilir demiştik, Dictionarylerde ise Indexli key vererek Item'a ulaşıyoruz.
DİKKAT: Key ismini Key propertysi ile kullanamayız. Zira bu property write-only'dir, yani sadece Key'in değerini değiştirmek için kullanılır.
Farkındayım, bütün bunlar şu an çok karışık geliyor olabilir. Aşağıdaki örnekler biraz daha aydınlanmanıza yarayacaktır. Biraz aşağıda tüm bunları derleyip toplayan bir örnek ve bir tablo daha göreceksiniz. Ondan sonra kendi örneklerinizi de yapınca konu iyice pekişecektir.
Dim dict As New Dictionary
dict.Add "elma", "apple"
'Tekil elemana read erişimi
Debug.Print dict("elma") 'Key'in kendisi ile Itema erişim. apple değerini verir
Debug.Print dict.Keys(0) 'Indeksli Key ile Key'e erişim. elma değerini verir
Debug.Print dict.Key("elma") 'Hata verir. Çünkü Key propertysi write-onlydir.
'Tekil elemanlara write erişimi
dict.Key("elma")="Elma" 'Key'in değeri değişti, onun lookupı olan Itemın değil. Yani elme Elme oldu.
dict("elma")="apple" 'Itemın değerini değişti. Eleman yoksa Implicit ekleme olur. Yani elma, apple ikilisi eklenir
dict.Item("apple")="Apple" 'Item'ın değerini değiştirdik.
Aşağıda ise erişim yöntemleriyle ilgili küçük bir collection/dictionary karşılaştırması bulunuyor.
Dim col As New Collection
Dim dict As New Scripting.Dictionary
'Collection örneği
col.Add "Elma" '1.index
col.Add "Armut" '2.index
col.Add "Erik" '3.index
Debug.Print col(2) 'veya col.Item(2). Armut yazar
'Key'li Coll örneği. ilk yazılan Item, ikincisi Key'dir
col.Add "Elma", "Apple"
col.Add "Armut", "Pear"
col.Add "Erik", "Plum"
Debug.Print col("Plum") 'Erik yazar
Debug.Print col("Erik") 'Hata verir. Erişim Item'la olmaz,
'Dictionary örneği. ilk yazılan Key, ikincisi Item'dır
dict.Add "Elma", "Apple"
dict.Add "Armut", "Pear"
dict.Add "Erik", "Plum"
Tüm elemanları dolaşma
Dictionary'lerin tüm elemanları dolaşmak için tüm dizimsi yapılarda olduğu gibi For Next döngülerini kullanıyoruz.
For Each döngüsünü hem Early Binding hem Late Binding için kullanabilirken, For Next döngüsünü sadece Early Binding tanımlama yapıldığında kullanabiliriz. (Binding çeşitleri hakkında bilgi için tıklayınız)
Dim k As Variant
For Each k In dict.Keys
Debug.Print k, dict(k)
Next k
For Each k In dict.Keys satırını For Each k In dict şeklinde de yazabilirdik. Yani sadece Dictionary'nin adını yazmak onun Keys propertysine bakacağız diye algılanır. Collectionlarda böyle bir yazımla Items kastedilir.
Klasik For döngüsünde ise dikkat edilecek husus, başlangıcın 0'dan başlaması, bitiş indeksinin de eleman sayısı - 1 olmasıdır.
Başka bir örnek
For i=0 To dict.Count-1
Debug.Print dict.Keys(i), dict.Items(i)
Next i
Başka bir örnek
Dim dict As New Scripting.Dictionary
dict.Add Key:="Apple", Item:="Elma"
dict.Add Key:="Orange", Item:="Portakal"
dict.Add Key:="Plum", Item:="Erik"
'Keylerin değerini değiştirebiliyoruz, yukardaki örnekte tek elemanın değerini
'değiştirmiştik, şimdi tüm elemaların başına "A_" koyuyoruz.
For Each k In dict.Keys
dict.Key(k) = "A_" & k
Next k
'tüm keyler ve bunların lookup değeri olan itemları yazdırıyoruz
For Each k In dict.Keys
Debug.Print k, dict(k)
Next k
İki kolonlu bir listeyi döngüsel olarak Dictionary'ye ekleme
Dim dict As Object 'Late binding ile yaratıyoruz
Set dict = CreateObject("Scripting.Dictionary")
Dim anahtar, deger 'tip belirtmeye gerek yok, Varianttırlar
Do
anahtar = ActiveCell.Value
deger = ActiveCell.Offset(0, 1).Value
If Not dict.Exists(anahtar) Then
dict.Add anahtar, deger
End If
ActiveCell.Offset(1, 0).Select
Loop Until IsEmpty(ActiveCell)
Debug.Print dict.Count
Key, Keys, Item ve Items birlikte kullanımı
Aşağıdaki iki örnek ile tüm bu öğrendiklerimizi pekiştirelim.
Sub foreach_in_dict()
Dim madenler As New Scripting.Dictionary
madenler.Add "gold", "altın"
madenler.Add "iron", "demir"
madenler.Item("diamond") = "elmas" 'madenler.Add "diamond", "elmas" ile aynı
madenler("cupper") = "bakır" 'üsttekinin kısa yöntemi
Nihai Özet Tablo
Aşağıdaki tablo ile de yukarıdaki örnekleri bir tablo şeklinde görüyoruz. İller isimli bir dictionary'miz olduğunu ve 01-Adana ile 33-İçel arasındaki kayıtların eklendiğini düşünün. Buna göre;
Üye | Ekleme | Erişim | Update |
---|---|---|---|
Item() propertysi | Olmayan kayıt key ile eklenir.Ör: Item(«34»)=«istanbul» | Dict içinde bulunan bir Item, Key kullanılarak okunur. Erişilen şey Item'dır. Ör: Item(«34»)-->istanbul | Dict içinde bulunan bir Item, Key kullanılarak değiştirilir. Ör:Item(«34»)=«İstanbul» |
Items() metod | Ekleme yapılamaz | Indeks no ile erişilir. Erişilen şey Item'dır. Ör: Items(0)-->adana | Etkisizdir. Update yapılamaz. |
Key() propertysi (write-only) |
Ekleme yapılamaz. Kullanılırsa hata alınır | Olmayan key’e erişemeyiz. Olanın ise içeriğini değiştiririz. Erişilen şey Key'dir. | Update yapılamaz |
Keys() metod | Ekleme yapılamaz | Indeks no ile erişilir. Erişilen şey Key'dir. Ör: Keys(0)--> «01» | Update yapılamaz |
Collectionların ve Dictionarylerin karşılaştırılması
Bu iki yapının benzerlikleri, farklılıkları ve birbirine göre avantaj/dezavantajları bulunmaktadır. "Şu daha iyidir" diye doğrudan bir söylem doğru değildir. Her araçta olduğu gibi, o an ihtiyacımızı en iyi hangisi görüyorsa onu kullanmamız gerekmektedir. Ben burada bir karşılaştırma vereceğim, kararı siz verin. Tabiki benim de naçizane bazı tavsiye ve yönlendirmelerim olmayacak değil.
- Collectionlar VBA içinde yerel olarak bulunurken, Dictionary'leri kullanmak için bunu ya reference olarak eklemeli ya da CreateObject ile Late Binding şekilde yaratmalıyız.
- Dictionary'lerde Key de Item da hem okunabilir hem yazılabilirdir. Ancak Collectionlarda Item'ın değerini değiştiremezsiniz, yani read-onlydir. Bunu yapmak için önce onu kaldırmalı sonra yeni değerle tekrar eklemeniz gerekir. Ayrıca Collectionlarda Keyler ne read-only ne write-onyldir, yani değer atanamadıkları gibi elde edilemezler de; sadece ilgili Item'a ulaşmada kullanılırlar.
- Collectionlar sıralıdır, Dictionarylerde sıra yoktur. Bu yüzden Collectionlar'a indeks numarası ile erişebilirken Dictionary'lerde indeks ile erişilemez. Bununla beraber Dictionary'lerin Keys ve Items metodları bunları 0 tabanlı bir dizi olarak döndürür, yani Key ve Item'lardan oluşan bir dizi 0 indekslidir. Ancak Collectionlarda indeks 1'den başlar.
- Her iki yapıda da elemanlarda dolaşmak için For Each yapısı kullanılır. Direkt ilgili nesnenin adı verilerek dolaşılmaya çalışıldığında, Collectionda itemlarda dolaşılırken Dictionarylerde Keylerde dolaşılır.
- Collectionları diziye atamak için döngüsel bir yapıyı içeren birkaç satırlık koda ihtiyaç duyulurken Dictionarylerde Items ve Keys metodları bize doğrudan dizi verirler.
- Dictionarylerde eleman eklemek için Add metoduna ek olarak implicit (üstü kapalı) ekleme yöntemi de varken, Collectionlarda yanlızca Add metodu kullanılır.
- Dictionarylerde tüm elemanlar tek seferde (RemoveAll ile) silinebilirken Collectionlarda dolaylı yöntemler izlenir.
- Keyler her ikisinde de benzersiz olmalıdır.
- Keyler Dictionaryde zorunlu iken Collectionda seçimliktir.
- Keyler Collectionlarda String olmalıyken, Dictionarylerde ise dizi dışında her şey olabilirler.
- Dictionarylerde bir elamanın var olup olmadığı Exists metodu ile kontrol edilebilirken Collectionlarda bu kontrol için birkaç satırlık kod yazmak gerekir.
- Collectionlar küçük/büyük harf ayrımına duyarlı değilken Dictionaryler duyarlıdır, istenirse duyarsız hale getirilebilir.
- Genel olarak bakıldığında, Dictionaryler Collectionlara göre daha hızlıdır.
Dictionary | Collection | |
---|---|---|
Parametreler |
İkisi de zorunlu |
Sadece Item zorunlu |
Vurgu |
Key, Item |
Item, (Key) |
Erişim |
Item(«key adı»)-->Item |
İndeks, Key-->Sadece item elde edilir. Key elde edilemez |
Örnek Kod
Sub coll_vs_dict()
Dim dict As New Dictionary 'library
Dim coll As New Collection 'no library
dict.Add "yüz", "hundred"
coll.Add "hundred", "yüz"
Debug.Print dict.Keys(0), dict.Items(0) 'iki değer de elde edilebilir
Debug.Print coll(1), coll("yüz") 'Key yani "yüz" değeri elde edilemez
dict.Item("yüz") = 100 'key'in lookup değeri olan item'ı değiştirebiliriz
Debug.Print dict.Keys(0), dict.Items(0)
coll(1) = 100 'hata. collectionlar readonlydir, itemlar dğeiştirilemez, keylere zaten ulaşamıyoruz bile
Debug.Print dict(0) 'dictionaryde indeks yoktur
Debug.Print coll(1) 'collectionlarda indeks var ve 1'den başlar
End Sub
Büyük üstat Cpearson der ki:
Her iki obje de benzer datayı gruplamak için çok faydalıdır ancak herşey eşit olduğunda ben Dictionary kullanmayı tercih ederim. Gerekçe olarak da yukarda belirttiğim maddelerden bazılarını dile getirmiş.
Benim de naçizane bir tavsiyem var: Eğer sadece arka arkaya birşeyler eklemek istiyorsanız ve kümeden birşeyler çıkarma veya Varmı kontrolü gibi şeyler yapmayacaksanız Collection kullanın, hem de Key'siz haliyle. Ama bir lookup değeri de olacaksa Key ve Item ikilisine yani bir Dictionary'ye ihtiyacınız var demektir.
İçiçe Dictionary (Dictionary of Dictionary)
Dizi Dizisi ve Collection Collectionı gibi Dictionarylerin de içiçe geçmiş formları vardır. Hatta yeri gelir, Dictionary of Collection, Collection of Dictionary veya Array of Collection gibi çapraz formlar da kullanmamız gerekebilir.
Benim şahsen çok ihtiyacım olmadı ancak internette bol miktarda örnek bulunmaktadır. Mesela şu sayfada hem Dictionaryler hakkında çok faydalı bilgiler hem de içiçe Dictionary dahil birçok örneği bulabilirsiniz.
Bununla birlikte gözünüzde canlanması için aşağıdaki gibi bir örnek kod yazabiliriz.
Sub dictofdict()
Dim m As New Scripting.Dictionary
Dim s As New Scripting.Dictionary
Dim dict As New Scripting.Dictionary
m.Add "elma", "apple"
m.Add "erik", "plum"
s.Add "salatalık", "cucumber"
s.Add "domates", "tomato"
dict.Add "meyveler", m
dict.Add "sebzeler", s
Debug.Print dict("meyveler")("elma") 'veya dict("meyveler").Item("elma")
End Sub
Bu arada ilk başta kulağa sanki Dictionary of Dictionary ile çözülebilirmiş gibi gelen bir problemi ben aşağıdaki gibi Dictionary ve 3 boyutlu dizi ile hallettim. Biraz üzerine düşününce farklı çözümler de üretilebiliyor. Mesela listenizi farklı bir formata getirip arkasından Dictionary tipli bir dizi ile de sorun çözülebilir.
Dictionary ve Dizi bir arada
Aşağıdaki gibi bir listemiz var ve bunu bir alttaki resimdeki hale getirmek istiyoruz. Bu liste her gün güncellenen bir personel dosyası ve bir alttaki hale gelmeli, yoksa buradan beslenen formüllerde hatalar olacağı gibi bölgelere giden otomatik maillerde yanlış kişilere yanlış mailler gidebilir. Böyle bir listenin her gün manuel bir şekilde işlenmesi de oldukça zahmetli olurdu. O yüzden aşağıdaki gibi bir kod yazmalıyız.
Getirmek istediğim hal ise şu. Bu hale geldikten sonra burdan beslenen birçok lookup formülü var.
İzlenecek yol:Blg ve Sgm isminde iki Dictionary tanımlarız. Bir de sicilleri atayacağımız bir dizi. Dizimiz çok boyutlu olabileceği gibi içiçe dizi şeklinde de olabilir. Ben burada çok boyutlu dizi yöntemini seçtim.
Sub dictvedizi()
Dim Blg As New Scripting.Dictionary
Dim Sgm As New Scripting.Dictionary
Dim Siciller() As String
ReDim Siciller(0 To 2, 0 To 3, 0 To 3) 'boyutlar sırayla şöyle: bölge sayısı, segment sayısı, kişi sayısı
Set alanBolge = Range(Range("a2"), Range("a2").End(xlDown))
Set alanSegment = Range(Range("c2"), Range("c2").End(xlDown))
i = 0
For Each d In alanBolge
If Not Blg.Exists(d.Value) Then
Blg.Add Key:=d.Value, Item:=i
i = i + 1
End If
Next d
k = 0
For Each d In alanSegment
If Not Sgm.Exists(d.Value) Then
Sgm.Add Key:=d.Value, Item:=k
k = k + 1
End If
Next d
'data okuma
For Each d In alanBolge
Siciller(Blg(d.Value), Sgm(d.Offset(0, 2).Value), dolusay(Siciller, Blg(d.Value), Sgm(d.Offset(0, 2).Value)) + 1) = d.Offset(0, 1).Value
Next d
'hedefe yazma
For i = 0 To Blg.Count - 1
Cells(i + 2, 5).Value = Blg.Keys(i)
Next i
For i = 0 To Sgm.Count - 1
Cells(1, i + 6).Value = Sgm.Keys(i)
Next i
For x = 1 To 4
For y = 1 To 3
Set h = Cells(1 + y, 5 + x)
h.Select
h.Value = sonucgetir(Siciller, Blg(h.Offset(0, -x).Value), Sgm(h.Offset(-y, 0).Value))
Next y
Next x
End Sub
Public Function dolusay(ByVal Data As Variant, ByVal i1 As Integer, ByVal i2 As Integer) As Integer
Dim Count As Integer
Count = 0
For j = 0 To UBound(Data, 3) - 1
If Len(Data(i1, i2, j)) > 0 Then
Count = Count + 1
End If
Next j
dolusay = Count
End Function
Public Function sonucgetir(ByVal Data As Variant, ByVal i1 As Integer, ByVal i2 As Integer) As String
sonucgetir = ""
For i = 0 To UBound(Data, 3)
If Len(Data(i1, i2, i)) > 0 Then
x = Data(i1, i2, i) & ";" & x
sonucgetir = Left(x, Len(x) - 1)
End If
Next i
End Function
Kodu biraz inceleyin, başka türlü nasıl yapılabilirdi, onu düşünün. Şükür ki, gerek Excel'de gerek VBA'de bir işi yapmanın birden çok yolu olabilmektedir. Artık aklınıza hangisi gelirse onun üzerine yoğunlaşın.
Her değer için min/maks değeri alıp depolama
Dictionarylerin Pratik Uygulamaları
Kişi isimlerinin birkaç kez geçtiği yerde herkesin en büyük satışını aldırma, herkese ait en küçük tarih buldurma gibi örnekler de dictionarylerin pratik uygulamaları arasındadır. Buradaki mantık şu şekilde işler: For döngüsü ile tüm değerler taranır ve sırayla eklenir, ancak ekleme yapılırken Exists ile "daha önce eklenmiş mi kontrolü" yapılır. Tabii ki, datanın ya manuel ya da kod içinde sıralanmış olması gerekir.
Aşağıdaki örnekte, belirli sicil numaralı kişilerin belirli şubelere başlama tarihleri var. Bir kişi zaman içinde bir şubeden başka şubeye tayin olabilmekte, o yüzden bazı kişilerin birden fazla satırda geçtiğini anlamak zor olmayacaktır. Biz burada bir kişinin bankaya en erken giriş tarihini bulmaya çalışacağız. Ör: 35516 için 14.09.2014 tarihini bulmalıyız, 38541 için de 28.05.2015.
Kodu uzatmamak adına listeyi manuel olarak sıralayalım.
Sub enbuyuktarih()
Dim st As New Scripting.Dictionary
Dim alan As Range, a As Range
Set alan = Range("A2:A7")
For Each a In alan
If Not st.Exists(a.Value2) Then
st.Add a.Value2, a.Offset(0, 2).Value
End If
Next a
For Each s In st
Debug.Print s, st(s)
Next s
End Sub
Eğer ki bu ilk şubenin hangisi olduğunu da öğrenmek isteseydik farklı bir şeyler daha yazmamız gerekirdi. Ben şube kodunu Collection'a ekleyerek bulma yöntemini denedim. Dizi kullanarak da çözülebilirdi.
Sub enbuyuktarih2()
Dim st As New Scripting.Dictionary
Dim col As New Collection
Dim alan As Range, a As Range
Set alan = Range("A2:A7")
For Each a In alan
If Not st.Exists(a.Value2) Then
st.Add a.Value2, a.Offset(0, 2).Value
col.Add a.Offset(0, 1).Value, CStr(a.Value) 'Keyler collectionlarda string olmalı
End If
Next a
For Each s In st
Debug.Print s, st(s), col(CStr(s))
Next s
End Sub
Dictionary tipli bir dizi
Gerçek bir sözlük uygulaması (İngilizce dışındakiler uydurmadır :))
Datamızı bu şekile getirdikten sonra bu yukardaki örnekteki ve benzer örneklerdeki sorunuda bu yöntemle çözebiliriz.
VBA Kodu
Sub arraydict()
Dim dict(2) As New Scripting.Dictionary
dict(0).Add "ekmek", "bread" 'ingilizce
dict(1).Add "ekmek", "brot" 'almanca
dict(2).Add "ekmek", "brotti" 'italyanca
'1000 satır boyunca 1000 kelimeyi okuyup atadık diyelim
'almancada ekmek ne demek
Debug.Print dict(1)("ekmek")
End Sub
- Akdeniz bölgesinin Ticari müdürü kim(diğer 3 segment müdürü de var)
- Ankarada Patates fiyatı(diğer 10 sebze fiyatı da var)
- Almanyada erkeklerin yaş ortalaması kaç(kadınların ortalaması da var)
- v.s v.s
Mesela bir üstteki sicil-tarih örneğini de bu yöntemle yazabiliriz.
Sub arraydict2()
Dim dict(1) As New Scripting.Dictionary
Dim alan As Range, a As Range
Set alan = Range("A2:A7")
For Each a In alan
If Not dict(0).Exists(a.Value2) Then 'herhangi birine bakılabilir
dict(0).Add a.Value, a.Offset(0, 1).Value 'şb
dict(1).Add a.Value, a.Offset(0, 2).Value 'tarih
End If
Next a
For Each s In dict(0)
Debug.Print s, dict(0)(s), dict(1)(s)
Next s
End Sub
Dictionary, Collection ve Collection Dizisi bir arada
Bu sitenin iletişim sayfasından ara ara benden destek isteyenler oluyor. Yine bir gün gelen bir mailde, aşağıdaki gibi bir talep vardı.
Parekende elektrik malzemesi satıyoruz. Satışı yapmamız için alış yapmış olmamız lazım. Bir malı x müşterisine y miktar satış yapmış isek o malın yine y miktar girişinin olması gerekiyor. Yani aynı müşteri adına aynı miktarda satış ve alış miktarı olması lazım. İşi sağlama bağlamak için E sütunundaki malzeme adını da ekledim. Fakat alınan malın satışı bir alt sütunda olacak diye bir kaide yok, herhangi bir yerde olabilir. Çünkü bu bir muhasebe çıktısının excel'e çevrilmiş hali. Normalde biz bu işi gelişmiş süzmelerle yapıyor ve tek tek satırları boyuyoruz. Bu da zaman alıyor tabii ki. Haftada birkaç çıktı aldığınızda problem oluyor. Aynı olan verileri boyamak istiyoruz. Veri yapısı şöyle: K sütünunda müşteri adı, G sütununda alış miktarı, I sütununda satış miktarı E sütununda stok ismi var. K sütununun satırlarında aynı müşteri adı varsa ve söz konusu satırlardaki alış miktarları(G sütunu satırları) satış miktarlarına(I sütunu satırları) eşitse ve E sütunundaki satır değerleri de eşitse A'dan K dahil o satır boyansın istiyorum. Koşullu biçimlendirme ile yapamadım. Buradaki amaç aynı miktarda olan mallar aynı müşteri için giriş çıkış yapmışsa o satırı boyayıp listeden elimine etmek......
İlk başta Conditional Formatting ile yapmayı denedim ancak alım ve satımın aynı olma durumu ardışık olmayan satırlarda da olabileceği için bundan vazgeçip aşağıdaki kodu hazırladım.
Sub mukerrerbul()
Dim dict As Object
Dim koll As New Collection
Dim babaKol() As Collection
Dim a As Range, s As Range
Dim i As Integer
Dim c As Variant
'babaKol collection dizisinin boyutunu buluyoruz
For Each a In Range([e2], [e2].End(xlDown))
If Not ColdaVarmı(koll, a.Value & a.Offset(0, 2).Value & a.Offset(0, 6).Value) Then
koll.Add a.Value & a.Offset(0, 2).Value & a.Offset(0, 6).Value
End If
Next a
Set dict = CreateObject("Scripting.Dictionary")
'önce g'nin boyutunu berlieyelim
ReDim babaKol(koll.Count - 1)
'sonra dolu olan alımları dictionary'ye ekliyoruz
For Each a In Range([e2], [e2].End(xlDown))
If Not dict.Exists(a.Value & a.Offset(0, 2).Value & a.Offset(0, 6).Value) And Not IsEmpty(a.Offset(0, 2)) Then
Set babaKol(i) = New Collection
dict.Add a.Value & a.Offset(0, 2).Value & a.Offset(0, 6).Value, babaKol(i)
babaKol(i).Add a.Row
i = i + 1
ElseIf dict.Exists(a.Value & a.Offset(0, 2).Value & a.Offset(0, 6).Value) And Not IsEmpty(a.Offset(0, 2)) Then
dict(a.Value & a.Offset(0, 2).Value & a.Offset(0, 6).Value).Add a.Row
End If
Next a
'şimdi satımları kontrol ediyruz. varsa, hem bunu hem de bunun alım karşılığını işaretliyoruz
For Each s In Range([e2], [e2].End(xlDown))
If dict.Exists(s.Value & s.Offset(0, 4).Value & s.Offset(0, 6).Value) Then
'önce satımı boyayalım
s.EntireRow.Font.Color = vbRed
'şimdi de alımları boyayalım
For Each c In dict(s.Value & s.Offset(0, 4).Value & s.Offset(0, 6).Value)
Rows(c).Font.Color = vbRed
Next c
End If
Next s
End Sub
Function ColdaVarmı(col As Collection, kontrol As Variant) As Boolean
On Error Resume Next
ColdaVarmı = False
Dim x As Variant
For Each x In col
If x = kontrol Then
ColdaVarmı = True
Exit Function
End If
Next x
End Function
Şimdi elimizde neler var bi bakalım:
koll:Toplam kaç değişik benzersiz kaydımız var, bunu tutacağımız koleksiyon.
dict:Firma-Stok-alım adedinden oluşan benzersiz kayıtları tutacak dictionary. Bunun value parametresinde ise ilk başta satır numarasını gösteren bir değişken kullanmıştım ancak sonradan farkettim ki, aynı kayda ait başka mükerrer kayıtlar da olabiliyor, o yüzden tek değer içeren bir değişken yerien bi collection kullanmak gerekiyor, ancak her kayıt için de farklı bir collection kullanmam gerektiği için bunu normal bir collection yerine "collection dizisi" (babaKol) şeklinde yarattım.
Sonra dolu olan alımları dictionary'ye ekledim.
Sonrasında, satım miktarlarını bu dictionary içinde var mı diye kontrol ettim, varsa hem kendisinin olduğu satırı hem de bunun dictionaryde karşılık gelen collectiondaki satırları yani alımların olduğu satırları boyattım.
Dosyanın kendisine de buradan ulaşabilirsiniz.