std::map ve std::multimap

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"] << '\n';

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

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

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\n";
}

Ş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 ö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 << '\n';    
}

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, "\n"));

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.

Anahtar olarak kullanıcı türlerinin kullanılması

İçindeki ağaç yapısının tutarlılığının korunabilmesi için, map'in nesnelerinin birbirlerinden kesin olarak ayırt edilebilmeleri gerekir. Bu yüzden; anahtar olarak bir kullanıcı türü kullandığımızda; nesneler arasında, karışıklığa yol açmayacak olan bir sıra ilişkisi tanımlamamız gerekir.

Bu sıra ilişkisini, anahtar olarak kullanılacak türün operator< işleci ile belirleriz. Bunun nasıl yapıldığını göstermek için, daha önceki örneklerde kullandığım rehber sınıfının anahtar ve nesne türlerinin yerlerini değiştirmek istiyorum. Bu durumda, tersine çalışan bir rehber elde etmiş olacağız: verilen bir telefon numarasına karşılık gelen isme erişeceğiz.

Yazıyı daha fazla uzatmamak için bu yeni programın bu bölümle ilgili olmayan satırlarını ya hiç göstermeyeceğim, ya da /* ... */ açıklamaları olarak vereceğim:

class TelefonNumarasi
{

/* ... */

public:

/* ... */

    /*
      Bu sinifin bir map anahtari olabilmesi icin
      gereken operator< islecini tanimliyoruz.

      Burada yapmamiz gereken, uzerinde calistigimiz
      nesnenin bize verilen 'diger' bir nesneden hangi
      durumda daha kucuk (yani siralamada daha once
      gelen) oldugunu belirlemektir.

      Burada tek yapmamiz gereken, nesnenin kucuk 
      oldugu durumda 'true' dondurmektir.
     */
    bool operator< (TelefonNumarasi const & diger) const
    {
        /*
          Tutarli oldugu surece baska herhangi bir
          siralama iliskisi de kurabiliriz. Ben, dogal
          oldugu icin once alan koduna, sonra da 
          numaranin kendisine bakacagim. Tanim soyle:

          Eger alan kodu diger nesnenin alan kodundan
          kucukse, bu nesne daha kucuktur.

          Eger alan kodlari esitse ama numara digerinin
          numarasindan kucukse, bu nesne kucuktur.

          Bunlarin disindaki durumlarda bu nesnenin diger
          nesneden daha kucuk oldugunu soyleyemeyiz. O
          yuzden o durumlarda, donus turumuz 'false'
          olacaktir.
         */
        return ((alanKodu_ < diger.alanKodu_)
                ||
                ((alanKodu_ == diger.alanKodu_)
                 &&
                 (numara_ < diger.numara_)));
    }
};


/*
   Bu tanimda anahtar olarak TelefonNumarasi kullaniliyor:
*/
typedef map<TelefonNumarasi, string> TersRehber;
typedef TersRehber::const_iterator KayitErisici;


/*
  Yukaridaki sinif ve tanimlari deneyen bir 'main'
  soyle yazilabilir:
*/
int main()
{
    // Rastgele numaralar
    TelefonNumarasi const numara1("1", "11111");
    TelefonNumarasi const numara2("5", "55555");
    TelefonNumarasi const numara3("2", "22222");
    
    // Rehberi dolduruyoruz
    TersRehber rehber;
    rehber[numara1] = "ali";
    rehber[numara2] = "veli";
    rehber[numara3] = "deli";

    // Numaralardan birisine ait olan kisiyi ariyoruz
    TelefonNumarasi const & arananNumara = numara1;
    KayitErisici const kayit = rehber.find(arananNumara);

    if (kayit == rehber.end())
    {
        cout << "Bulamadim! Halbuki daha yeni eklemistim (?)";
    }
    else
    {
        cout << arananNumara << " numarali telefon '"
             << kayit->second << "' adli kisiye ait";
    }
    
    cout << '\n';
}

Aramayı yaparken map'in öge işlevi olan 'find'ı (std::map::find) kullandım. Onun yerine serbest işlev olan 'find' algoritmasını (std::find) da kullanabilirdim. st::map::find'ı yeğlememin nedeni, onun map'in iç ağaç yapısından haberli olması ve bu yüzden işini daha çabuk yapabilmesidir. std::find ise aramayı baştan sona sıralı olarak yapar.

multimap

multimap; map gibi çalışan, ama aynı anahtara sahip birden fazla nesneyi içinde barındırabilen bir topluluktur. Bu özelliği yüzünden, operator[] işlecini sunamaz: Çünkü o işlecin döndürebileceği birden fazla nesne bulunabilir.

multimap'in find işlevi, anahtara uyan ilk nesneye erişim sağlar. Aynı anahtara sahip sonraki nesnelere erişmek için erişiciyi kendimiz ilerletebiliriz.

Bunun dışında, multimap'in lower_bound ve upper_bound işlevlerini kullanarak bir anahtara uyan bütün nesnelerin topluluk içinde hangi aralıkta bulunduğunu da öğrenebiliriz. lower_bound ve upper_bound'un ikisinin birden işlerini yapabilen equal_range de aynı amaçla kullanılabilir.

multimap'in bir örneğini, özel bazı sayıların ne olduklarını bilen bir topluluk üzerinde göstermek istiyorum. Bu örnekte hem 'find', hem de 'equal_range' yöntemlerini kullanacağım.

#include <map>
#include <string>
#include <iostream>
#include <iterator>

using namespace std;

/*
  multimap de map gibi kullanilir. Birinci
  sablon parametresi anahtarin, ikincisi de
  nesnelerin turunu belirler.
 */
typedef multimap<int, string> SayiSozlugu;
typedef SayiSozlugu::const_iterator KayitErisici;
typedef SayiSozlugu::value_type Kayit;

/*
  Bilginin ilk halini bu ornekte bir yapida tutuyorum.
 */
struct SayiBilgisi
{
    int sayi;
    char const * anlam;
};

/*
  Bilgi bu ornekte bu sabit diziden geliyor ama
  herhangi bir kaynaktan da geliyor olabilirdi.
*/
SayiBilgisi const bilinenSayilar[] =
{
    { 7, "haftadaki gun sayisi" },
    { 1, "bir" },
    { 60, "dakikadaki saniye sayisi" },
    { 10, "eldeki parmak sayisi" },
    { 60, "saatteki dakika sayisi" },
    { 7, "guzel bir asal sayi" },
    { 42, "herseyin yaniti" },
    { 11, "on arti bir" },
};

#define TOPLAM_OGE(x) (sizeof(x) / sizeof(*(x)))


/*
  Sozlugu doldurma isinde yardimci olmasi icin
  islev nesnelerinin bir ornegini kullanacagim.[5]

  Bu sinifin yaptigi sey, kurulurken kendisine
  verilen SayiSozlugu'nu aklinda tutmasi ve
  islev gibi kullanilirken kendisine verilen
  her bir nesneyi o sozluge eklemesidir.
 */
class SozlugeKaydeden
{
    SayiSozlugu & sozluk_;

public:

    explicit SozlugeKaydeden(SayiSozlugu & sozluk)
        :
        sozluk_(sozluk)
    {}

    void operator() (SayiBilgisi const & bilgi) const
    {
        /*
           Yazida anlatmadigim halde burada kullandigim
           'insert' islevi, map ve multimap'e oge eklemek
           icin kullanilir.

           map ve multimap'in ogelerinin turleri 'pair'
           oldugundan, burada 'make_pair' islevinden
           yararlanabiliyorum.
        */

        sozluk_.insert(make_pair(bilgi.sayi, bilgi.anlam));
    }
};

SayiSozlugu sozlukKur()
{
    SayiSozlugu sonuc;

    /*
      Bu yazinin konusundan ayriliyor olsam da,
      asagidaki satirda sirasiyla neler oldugunu
      soylemek istiyorum:

      Once adsiz gecici bir SozlugeKaydeden nesnesi
      olusturuluyor. Bu yapilirken, kendisine 'sonuc'
      adli topluluk bildiriliyor.

      for_each, bilinenSayilar icindeki her bilgiyi
      SozlugeKaydeden::operator() islevine gonderiyor.
      O islev de aldigi bilgiyi kendisine bildirilen
      sozluge ekliyor.
     */
    for_each(bilinenSayilar,
             bilinenSayilar + TOPLAM_OGE(bilinenSayilar),
             SozlugeKaydeden(sonuc));

    return sonuc;
}

void kayitYazdir(Kayit const & kayit)
{
    cout << kayit.first << " \""
         << kayit.second << "\" anlamina gelir\n";
}

void neBileyim(int sayi)
{
    cout << "Hic " << sayi << " diye bir sayi duymadim\n";
}

void bilgiVer_find_ile(SayiSozlugu const & sozluk, int sayi)
{
    KayitErisici erisici = sozluk.find(sayi);

    if (erisici == sozluk.end())
    {
        neBileyim(sayi);
    }
    else
    {
        /*
          find islevini multimap ile kullanarak anahtara
          uyan nesnelerden ilkine erisebiliriz.
          
          Bu durumda, nesnelerin hep sirali olarak
          tutulduklarini bildigimiz icin, anahtar ayni
          oldugu surece erisiciyi ilerleterek butun
          kayitlari gorebiliriz.

          Bunu yaparken 'erisici'nin sozlugun sonunda 
          olmadigini oncelikle denetlememiz gerekir.
        */

        for ( ; (erisici != sozluk.end())
                &&
                (erisici->first == sayi); ++erisici)
        {
            kayitYazdir(*erisici);
        }
    }
}

void bilgiVer_aralik_ile(SayiSozlugu const & sozluk, int sayi)
{
    /*
      equal_range, aradigimiz anahtara uyan nesnelerin
      basini ve sonunu bir 'pair' icindeki iki erisici
      halinde dondurur.

      Bu donus degerinin 'first' ogesi araligin basini,
      'second' ogesi de araligin sonunu belirler.

      equal_range'in dondurdugu bu bir cift erisiciyi
      ayri ayri 'lower_bound' ve 'upper_bound' islevlerini
      cagirarak da edinebiliriz. Onlari bu yazida
      anlatmaya gerek gormuyorum.
    */
    pair<KayitErisici, KayitErisici> const aralik
        = sozluk.equal_range(sayi);

    if (aralik.first == sozluk.end())
    {
        neBileyim(sayi);
    }
    else
    {
        for (KayitErisici erisici = aralik.first;
             erisici != aralik.second;
             ++erisici)
        {
            kayitYazdir(*erisici);
        }
    }
}

void bilgiVer(SayiSozlugu const & sozluk, int sayi)
{
    cout << "\n--- find ile ---\n";
    bilgiVer_find_ile(sozluk, sayi);

    cout << "\n--- equal_range ile ---\n";
    bilgiVer_aralik_ile(sozluk, sayi);
}   

int main()
{
    SayiSozlugu const sozluk(sozlukKur());

    /*
      Simdi 7 sayisinin ne anlama geldigini bu sozlukte
      bulmaya calisacagim.
    */
    bilgiVer(sozluk, 7);

    /*
      Bir tane de bilinmeyen sayi bulmaya calisalim:
     */
    bilgiVer(sozluk, 2003);
}

map ve multimap'ten nesne çıkartmak

Bu topluluklardan nesne çıkartmanın bir yolu, map::erase işlevini çıkartılacak nesnenin anahtarıyla çağırmaktır. Örneğin:

    rehber.erase("kaya");

Standart topluluklardan nesne çıkartırken, bazı erişicilerin geçersiz hale gelebileceklerini akılda tutmak gerekir. Her standart topluluk, hangi işlemlerin hangi erişicileri hangi koşullarda geçersiz hale getirdiğini belgelemek zorundadır.

Örneğin; bir vector topluluğunun sonuna nesne eklemek, vector'ün bütün içeriğinin başka bir alana kopyalanmasını gerektirebildiği için, o vector içindeki nesneleri gösteren her bir erişici geçersiz hale gelebilir. Öte yandan, vector'ün ortasından bir nesne çıkartmak, yalnızca onu ve sonraki nesneleri gösteren erişicileri geçersiz hale getirir; ondan önceki nesneleri gösteren erişiciler etkilenmezler.

map ve multimap'ten nesne çıkartmak, o nesneyi gösteren erişiciyi geçersiz hale getirir. Diğer nesneleri gösteren erişiciler bu işlemden etkilenmezler.

Nesne çıkartmanın başka bir yolu da; bir nesne çıkartmak için o nesneyi gösteren tek bir erişici, birden fazla erişici çıkartmak için ise o nesneleri belirleyen bir çift erişici kullanmaktır.

Bunu gösteren bir program da şöyle yazılabilir:

#include <map>
#include <iostream>
#include <string>
#include <iterator>

using namespace std;

typedef multimap<string, string> Sozluk;
typedef Sozluk::value_type Kayit;

/*
  Yine bu yazinin konusundan ayriliyorum. Ancak,
  bir 'Kayit'i cikisa gonderen islevi neden
  'std' ad alani icerisinde tanimlamak zorunda
  kaldigimi anlatmak gerektigine inaniyorum.

  Fazla ayrintiya girmeden sunlari soyleyebilirim:
  Hemen asagidaki operator<< islecinin
  cikarsanmasinda kullanilan butun parametreler
  std ad alani icerisinde tanimlanmislardir:
  Kayit'in taniminda kullanilan multimap ve string
  turlerinin her ikisi de std ad alani icindedirler.

  O yuzden, asagida kullanilan copy algoritmasi
  kendi icerisinde dolayli yoldan

      cout << nesne;

  yaptiginda, uygun bir operator<< isleci
  ancak std ad alani icinde aranir.

  Derleme zamaninda yapilan bu aramanin basariya
  ulasmasi icin, bu operator<< islecini std ad
  alani icinde tanimlamak gerekir.

  Normalde kendi turlerimizle calisirken boyle
  bir ayrintiyla ugrasmak zorunda kalmayiz. Cunku
  o zaman zaten operator<< islecini normal olarak
  kendi turumuzu tanimladigimiz ad alani icinde
  tanimlariz.

  Yani, ben burada 'namespace std' diyorum ama
  cogu durumda boyle bir seye gerek olmaz. :)
*/

namespace std
{

ostream & operator<< (ostream & cikis,
                      Kayit const & kayit)
{
    return cikis << kayit.first << ": " << kayit.second;
}
 
}

void altiCiziliBaslik(string const & baslik)
{
    cout << '\n' << baslik << '\n';

    /*
      fill_n, bir nesneyi bir hedefe belirli bir
      sayida kopyalamak icin kullanilir. Burada,
      '-' karakterini baslik.size() kere cout'a
      gonderiyoruz. Her bir kopyanin arasina ise
      "", yani hicbir sey koyuyoruz. :)
     */
    fill_n(ostream_iterator<char>(cout, ""),
           baslik.size(), '-');

    cout << '\n';
}

template <class Topluluk>
void cikisaGonder(string const & baslik,
                  Topluluk const & topluluk)
{
    typedef typename Topluluk::value_type NesneTuru;

    altiCiziliBaslik(baslik);
    
    copy(topluluk.begin(),
         topluluk.end(),
         ostream_iterator<NesneTuru>(cout, "\n"));
}

void kayitEkle(Sozluk & sozluk,
               string const & sozcuk,
               string const & karsilik)
{
    sozluk.insert(make_pair(sozcuk, karsilik));
}

void doldur(Sozluk & sozluk)
{
    kayitEkle(sozluk, "bin", "climb on");
    kayitEkle(sozluk, "ac", "open");
    kayitEkle(sozluk, "masa", "table");
    kayitEkle(sozluk, "at", "throw");
    kayitEkle(sozluk, "ac", "hungry");
    kayitEkle(sozluk, "at", "horse");
    kayitEkle(sozluk, "kapi", "door");
    kayitEkle(sozluk, "bin", "thousand");
}

int main()
{
    Sozluk sozluk;
    doldur(sozluk);
    cikisaGonder("Baslangicta", sozluk);

    sozluk.erase("kapi");
    cikisaGonder("'kapi' ciktiktan sonra", sozluk);

    sozluk.erase(sozluk.lower_bound("at"),
                 sozluk.upper_bound("bin"));
    cikisaGonder("'at'in basindan 'bin'in sonuna kadar"
                 " ciktiktan sonra", sozluk);
}

Özet

Bu yazıda, çok yararlı bulduğum map ve multimap topluluklarının kullanımlarını göstermeye çalıştım. Bunu yaparken bazı standart yapı ve algoritmaları da tanıttım.

Referanslar

[1] Türden bağımsız bir toplama algoritması: http://ceviz.net/index.php?case=article&id=170

[2] C++'ın türden bağımsız toplulukları (generic containers): http://ceviz.net/index.php?case=article&id=184

[3] std::vector temel işlemleri: http://ceviz.net/index.php?case=article&id=202

[4] std::vector ileri düzey işlemleri: http://ceviz.net/index.php?case=article&id=203

[5] İşlev Nesneleri ('Function Objects' veya 'Functors'): http://ceviz.net/index.php?case=article&id=215

[6] Aslında sıralama için kullanılan işlev, less<string>'dir. operator< işleci doğrudan kullanılmaz; onu less<string> çağırır.


Ali Çehreli - acehreli@yahoo.com