Üye GİRİŞİ

Son eklenen makale ve haberler

  Programlama » C / C++

std::map ve std::multimap

Ceviz.Net Pdf Çıktısı Al
 
Ceviz.Net Doc Çıktısı Al
 
Bu yazıda C++ toplulukları içerisinde benim belki de en sık kullandığım topluluk olan std::map topluluğunu ve onun biraz daha farklı bir türü olan std::multimap'i tanıtacağım. Tam adı std::map olduğu halde, okunurluğu arttırmış olmak için yazının
geri kalanında ad alanı belirtecini kullanmadan yalnızca 'map' yazacağım.

Bu toplulukları anlatırken, onların ögelerini tutmak için kullandıkları std::pair yapısını da tanıtmak gerekecek.

Bu yazıya devam etmeden önce, içerisindeki kavramlara temel oluşturduğunu düşündüğüm [1] ve [2] numaralı referanslardaki yazıları okumak isteyebilirsiniz.

C++ topluluklarından en basiti olduğu söylenebilecek olan std::vector ile ilgili iki yazıyı da [3] ve [4]'te bulabilirsiniz.

İlişkili Diziler (associative array)

map, bir ilişkili dizi gerçeklemesidir. Bunun ne olduğuna geçmeden önce, sıradan C dizilerinin de aslında ilişkili dizilerin özel bir hali olarak düşünülebileceklerini göstermek
istiyorum.

Bilindiği gibi, öge erişim işleci (operator[]), C dizileriyle birlikte kullanıldığında parametre olarak bir sıra numarası alır:

double dizi[] = { 1.23, 4.56 };
    size_t sira = 0;
    dizi[sira] = 7.89;


Doğal olarak, bu kullanıma bakarak operator[] işlecinin belli bir sıradaki ögeye erişmek için kullanıldığını düşünebiliriz. Yani, verilen bir sıra numarasına karşılık dizi içindeki bir
ögeye erişiriz.

Ancak; bu davranışı, verilen tamsayı ile "ilişkili olan" nesneye erişmek olarak da ifade edebiliriz. Bunu yaptığımızda, işlece verilen tamsayının aslında bir anahtar olduğunu söyleyebiliriz: Bir dizi nesne içerisinden, tamsayı türünden bir anahtarla ilişkili olan bir tanesine erişiriz.

Eğer bu anahtar kavramını tamsayılardan başka türlere de genişletirsek, ilişkili dizilerin kısa  bir tanımına varmış oluruz: içindeki nesnelere belirli anahtarlara göre erişim
sağlayan topluluk.

map ile kullanılan anahtar ve nesnelerin türleri, belli koşulları sağladıkları sürece herhangi türden olabilirler. Elimizde map ile gerçeklenmiş bir telefon rehberi olduğunu ve
rehberdeki numaralara kişilerin adlarıyla erişildiğini düşünürsek, map'i örneğin şöyle kullanabiliriz:

    TelefonNumarasi & numara = rehber["ali"];

Burada rehber'in bir dizi gibi kullanıldığına, ama erişim sırasında tamsayı yerine dizgi gönderildiğine dikkat edin.

map de aslında bir sınıf şablonu olduğu için, kullanılmadan önce anahtar ve nesnelerinin hangi türlerden olduklarının bildirilmesi gerekir. Bunu, map şablonunun iki zorunlu
parametresi ile bildiririz: Birinci parametre anahtarın, ikinci parametre ise nesnelerin türünü belirler. Örneğin, yukarıdaki telefon rehberini anahtarlar 'string', ve içindeki nesneler 'TelefonNumarasi' türlerinden olacak şekilde şöyle tanımlarız:

    map<string, TelefonNumarasi> rehber;

Bu telefon rehberini kullanan küçük bir programa geçiyorum. Bu program, map'in operator[] işlecinin davranışının ilginç bir yönünü de gösteriyor:

#include <map>
#include <iostream>

using namespace std;

class TelefonNumarasi
{
    friend ostream & operator<< (ostream &,
                                 TelefonNumarasi const &);
    string alanKodu_;
    string numara_;

public:

    /*
        Varsayilan parametre degerleri nedeniyle bu
        kurucu, ayni zamanda bir varsayilan kurucudur
        (default constructor)
    */
    explicit TelefonNumarasi(string const & alanKodu = "-",
                             string const & numara = "-----")
        :
        alanKodu_(alanKodu),
        numara_(numara)
    {}
};

ostream & operator<< (ostream & cikis,
                      TelefonNumarasi const & numara)
{
    return cikis << '(' << numara.alanKodu_ << ") "
                 << numara.numara_;
}

int main()
{
    /*
        Toplulugun tanimi
    */
    map<string, TelefonNumarasi> rehber;

    /*
        Icinin doldurulmasi
    */
    rehber["ali"]  = TelefonNumarasi("1", "23456");
    rehber["veli"] = TelefonNumarasi("5", "67890");

    cout << "Ali'nin numarasi : " << rehber["ali"] << '
';

    /*
        Icinde bulunmayan bir nesnenin yazdirilmasi
    */
    cout << "Kaya'nin numarasi: " << rehber["kaya"] << '
';

    cout << "Toplam telefon numarasi: " << rehber.size() <<
'
';
}



TelefonNumarasi sınıfının [2] numaralı referansın 'topluluk ögesi olma koşulları' bölümünde anlatılanlara uygun olarak bir varsayılan kurucu sunduğuna dikkat edin.

Bu programda ilk önce "ali" ve "veli" adlı kayıtları topluluğa ekliyoruz. Daha sonra da önce toplulukta bulunan ("ali") ve bulunmayan ("kaya") iki kaydın telefon numaralarını çıkışa yazdırıyoruz.

Toplulukta bulunmayan kayıt için de bir telefon numarası yazdırıldığına ve bunun sonucunda da toplam öge sayısının üç olduğuna dikkat edin. Sonuçta toplulukta şu kayıtlar bulunmaktadır:

ali   (1) 23456
    kaya  (-) -----
    veli  (5) 67890

Bunun nedeni, map::operator[] işlecinin aslında şu algoritmayı işletmesidir:

    1) eğer anahtar toplulukta yoksa
       {
           topluluğa o anahtara karşılık bir nesne ekle
           (nesneyi varsayılan kurucusuyla kur)
       }

    2) o nesneye erişim sağla

Bu garip davranışın bir nedeni, histogram türü uygulamaları kolaylaştırmaktır. Girişten gelen sözcüklerin kaç kere görüldüklerini sayan şu programın ne kadar kolaylıkla yazılabildiğine bakın:

#include <map>
#include <iostream>

using namespace std;

int main()
{
    map<string, unsigned long> toplamlar;

    string sozcuk;
    while (cin >> sozcuk)
    {
        ++toplamlar[sozcuk];
    }

    cout << "Alfabetik siradaki ilk sozcuk olan '"
         << toplamlar.begin()->first << "' sozcugunu "
         << toplamlar.begin()->second << " kere gordum
";
}


Şimdilik toplamlar.begin() kullanımını bir kenara bırakın;
map'in ögelerinin türlerini biraz sonra anlatacağım.

Burada göstermek istediğim şey, bu programın işinin (sözcüklerin girişte kaç kere görüldüklerinin sayılmasının) tek bir satırda yapılabilmesidir. ++toplamlar[sozcuk] satırını şöyle açıklayabilirim:

    1) eğer 'sozcuk' toplulukta yoksa
       {
           topluluğa o sözcüğe karşılık gelen bir nesne ekle

           Bu topluluktaki nesnelerin türleri 'unsigned long'
           olduğu için, ve 'unsigned long' türünden nesnelerin
           varsayılan kurucuları onlara 0 değerini atadığı için,
           bu durumda 'sozcuk' anahtarına karşılık elimizde 0
           değeri olacaktır.
       }

    2) o nesneye erişim sağla

    3) o nesnenin değerini bir arttır

Yani o satır işletildiğinde, ilk defa görülen bir sözcüğün değeri 1 olur.

operator[] işlecinin garip kabul edilebilecek bu davranışı, bu tür bir programda kolaylık sağlar. Ancak, yanlış bir kullanımda topluluğa sessizce istenmeyen ögeler eklenmesine de neden olabilir.

Buraya kadar anlattıklarımın std::map topluluk şablonunu hemen kullanmaya başlamak için yeterli olduğuna inanıyorum. Kısaca:

- <map> başlığını kaynak koda ekleyin

- map'i anahtar ve nesne türlerini belirtecek şekilde tanımlayın

- operator[] işleci ile nesneleri hem ekleyin hem de onlara
erişin


map ögesi olarak std::pair


İngilizce'de "çift" anlamına gelen pair, herhangi türden iki nesneyi bir araya getirmek amacıyla kullanılır. pair nesnelerinin her kullanımda neleri temsil ettikleri baştan
bilinemeyeceği için; birinci ve ikinci ögelerine İngilizce karşılıkları olan, sırasıyla 'first' ve 'second' adları verilmiştir.

Tanımladığı tür adlarını ve şablon kurucusunu çıkartırsak, tanımı aşağıdaki programdaki Cift yapı şablonu kadar basittir. Her 'pair' nesnesi kurulurken ögelerinin türlerini de açıkça belirtmek zorunda kalmayalım diye 'make_pair' adlı bir işlev de sunulmuştur.

İşlev şablonları şablon parametrelerinin türlerini işlev parametrelerinden çıkarsayabildikleri için, her seferinde öge türlerini belirtmemek için 'make_pair'i kullanabiliriz.

Bu küçük programda 'make_pair'in Türkçe tanımını da CiftYap adıyla veriyorum:

#include <iostream>
#include <string>

using namespace std;

/*
  Bu yapi cok sade; yalnizca iki tane ogesi var
 */
template <class Tur1, class Tur2>
struct Cift
{
    Tur1 birinci;
    Tur2 ikinci;

    Cift(Tur1 const & b, Tur2 const & i)
        :
        birinci(b),
        ikinci(i)
    {}
};

/*
  Sablon parametrelerinin turlerini
  her seferinde bildirmek zorunda kalmayalim
  diye, boyle bir islev tanimliyoruz.
 */
template <class Tur1, class Tur2>
Cift<Tur1, Tur2> CiftYap(Tur1 const & birinci,
                         Tur2 const & ikinci)
{
    return Cift<Tur1, Tur2>(birinci, ikinci);
}

// Bu islevin konumuzla bir ilgisi yok :)
template <class Gun>
void yazdir(Gun const & gun)
{
    cout << "sayiyla: " << gun.birinci
         << " yaziyla: " << gun.ikinci << '
';   
}

int main()
{
    string const pazartesi("pazartesi");
    string const sali("sali");
   
    // CiftYap islevini kullanmadan:
    Cift<int, string> gun(1, pazartesi);
    yazdir(gun);
   
    /*
      CiftYap islevi kolaylik sagliyor. Eger
      olmasaydi, soyle yazmak zorunda kalirdik:

      gun = Cift<int, string>(2, sali);
    */
    gun = CiftYap(2, sali);
    yazdir(gun);
}


pair'in map ile olan ilişkisi; anahtar/nesne çiftlerinin map'in içerisinde pair nesneleri olarak tutulmalarıdır. İçindeki nesnelere map erişicileri ile erişildiğinde, döndürülen pair'in
'first' ögesi anahtarı, 'second' ögesi de nesneyi temsil eder. Yani, bir map'teki nesnelere erişirken hem anahtar hem de nesne, kullanıma hazır olarak elimizin altındadır.

Bunun kullanımını göstermek için telefon rehberi programını rehberdeki bütün kayıtları yazdıracak şekilde değiştireceğim. Bunu yaparken şablon kullanımının getirdiği karmaşık yazımdan kurtulmak için typedef'ler de kullanacağım:

  typedef map<string, TelefonNumarasi> Rehber;
  typedef Rehber::value_type RehberKaydi;

map::value_type, zaten map'in bizim için kolaylık olsun diye sunduğu bir typedef'tir. Yukarıdaki satır yerine açıkça

  typedef pair<string, TelefonNumarasi> RehberKaydi;

satırını da kullanabilirdim. Ancak bu durumda, ileride rehberin türlerinde bir değişiklik yapmak gerektiğinde bu satırı da değiştirmem gerekirdi. Bu olası değişiklik zahmetinden
kurtardığı ve hata yapma riskini azalttığı için value_type yöntemini kullanmak daha iyidir.

Tür adlarını typedef'le tanımladıktan sonra artık tek bir rehber kaydını bir çıkış akımına gönderen işlevi yazabilirim:

/*
  Tek bir kaydi yazdiran bir islev
 */
ostream & operator<< (ostream & cikis,
                      RehberKaydi const & kayit)
{
    return cikis << kayit.first << ": " << kayit.second;
}


Şimdi yapılması gereken son şey, bütün kayıtları baştan sona çıkışa göndermektir. Bu işi, işlev nesnelerini anlattığım önceki bir yazımda da yaptığım gibi, for döngülerini açıkça yazmak yerine 'copy' algoritmasını kullanarak yapacağım.[5] Eğer
main'in sonuna

  copy(rehber.begin(),
         rehber.end(),
         ostream_iterator<RehberKaydi>(cout, "
"));

ifadesini eklerseniz, rehberdeki kayıtların çıkışa şu şekilde yazdırıldıklarını görürsünüz:

 ali: (1) 23456
  kaya: (-) -----
  veli: (5) 67890


map, içindeki nesneleri sıralı olarak tutar
------------------------------------------

Yukarıdaki çıkışta kayıtların alfabetik sırada a'dan z'ye doğru belirmeleri tesadüf değildir. Standart, map'in nesnelere logaritmik karmaşıklıkta erişim sağlamasını şart koşar. Bunu
sağlamak için map, içindeki nesneleri bir ağaç yapısında tutar.

Bu ağaçtaki nesnelerin hangi sırada sıralanacaklarını map'e kendimiz bildirebiliriz. Bu sıralama yönteminin yukarıdaki programda olduğu gibi, özellikle belirtilmediği durumlarda; map sıralamayı anahtar olarak kullanılan türe operator< işlecini uygulayarak oluşturur. Yani, bizim örneğimizde kullanılan sıralama işleci, operator<(string const &, string const
&)'tır.[6]

O işleç de string'lerin alfabetik sıradaki önceliğine göre sonuç bildirdiği için, nesneler alfabetik sırada sıralanırlar.

Sıralama algoritmasını map'e üçüncü şablon parametresi ile bildirebiliriz. Örneğin kayıtları alfabetik olarak ters sırada sıralamak için map'i şöyle kullanabiliriz:

  typedef greater<string> AlfabetikTers;
  typedef map<string, TelefonNumarasi, AlfabetikTers> Rehber;


Burada dikkat edilmesi gereken bir nokta, bu Rehber türünün önceki kullandığımız türle uyumsuz hale gelmiş olmasıdır. Bunun nedeni; sınıf şablonlarının türlerini, şablon parametrelerinin belirlemesidir. Sonuçta, örneğin programda

    map<string, TelefonNumarasi>

türünün beklendiği bir yerde

    map<string, TelefonNumarasi, AlfabetikTers>

türünü kullanamayız; ikisi ayrı türlerdir.


acehreli 09.11.2003
Sayfalar: 1 2


co.mments  del.icio.us  digg  Furl  NewsVine  Reddit  Spurl  TailRank  Wists   



Rating : 10 üzerinden 9.00
 



Tümünü Göster / Sadece Başlıklar Yorumlar

mehmet sağolun
varolun
 




yorum Yorum ekle
İsminiz:
Mailiniz:
Yorum Konu:
Soru: Türkiye'nin başkenti?
Cevap :
Bütün alanları doldurmanız gerekmektedir.

 
XHTML 1.0 CSS 2.1
Ceviz Reklam