Giriş
set, multiset, map, multimap associative container olarak anılırlar. STL'deki tüm veri yapıları gibi Map te non-instrusive bir sınıftır.
Non-Intrusive Veri Yapısı Nedir?
Eğer bir veri yapısı içerdiği veri nesnesinden yardım istemiyorsa yani aşağıdaki gibiyse non-intrusive - işe karışmayan - olarak adlandırılır. Aşağıdaki kodda kullanıcı nesnesi düğümler için kendi sınıfında alan sağlamak zorunda değil.
Eğer bir veri yapısı işleyebilmek için kullanıcı tarafından sağlanan veri nesnesinden yardım istiyorsa intrusive - işe karışan - olarak adlandırılır. Aşağıdaki kodda kullanıcı nesnesinin left ve right düğümler için değişken sağlaması gerekiyor.
Referans tipler value olarak tanımlanamaz. Aşağıdaki kod derlenmez.
C++11 ile static initalization yapmak kolay. Eğer C++11 kullanmıyorsak, boost::assign kullanılabilir.
Süslü parantezler içine key,value değerleri yazılır. Şöyle yaparız.
Iterator ve auto ile map'i dolaşmak için şöyle yaparız.
Sıralama için Strict Weak Ordering kullanılır.
Default Comparator
map veri yapısına dışarıdan comparator vermezsek std::less()'i kullanır.
< operator tanımlamak
Bir çok primitif tipin ve STL sınıfının < operatörü var. Eğer kendi key sınıfımıza da tanımlamak istersek ya global bir < operatörü tanımlarız ya da member operator yaparız.
Büyükten küçüğe doğru sıralama yapması için 3. parametreye sıralama algoritması verilebilir. Örnekte std::greater veriliyor.
explicit map (const key_compare& comp = key_compare().....);
şeklinde yaratıldığı için çalışıyor.
Functor Tanımlamak
Örnek'te functor tanımlanıyor ve yine STL örneğinde olduğu gibi functor nesnesini yaratmamız gerekmiyor. Burada dikkat edilmesi gereken nokta, functor'ın operatörü const olmalı. Yani verilen key'leri değiştirmeyeceğini garanti etmeli!
Function Pointer Tanımlamak
Function Pointer kullanmak diğer seçenekler arasında en zor olanı. Kendi function pointer'ımızı kullanmak istesersek, aşağıdaki Case Insensitive örneğinde olduğu gibi function pointer imzasını template içinde tanımlamak ve constructor metoduna da function metodunu geçmek gerekirdi.
Case Insensitive Key
Bazen map'in anahtar alanı string ise ve örnekteki gibi case insensitive anahtar kullanmak istiyorsak
set, multiset, map, multimap associative container olarak anılırlar. STL'deki tüm veri yapıları gibi Map te non-instrusive bir sınıftır.
Non-Intrusive Veri Yapısı Nedir?
Eğer bir veri yapısı içerdiği veri nesnesinden yardım istemiyorsa yani aşağıdaki gibiyse non-intrusive - işe karışmayan - olarak adlandırılır. Aşağıdaki kodda kullanıcı nesnesi düğümler için kendi sınıfında alan sağlamak zorunda değil.
struct MyData {
// data to be stored
};
template <class T>
class RbTree {
// implementation
};
// usage:
RbTree<MyData> treeInstance;
Intrusive Veri Yapısı Nedir?Eğer bir veri yapısı işleyebilmek için kullanıcı tarafından sağlanan veri nesnesinden yardım istiyorsa intrusive - işe karışan - olarak adlandırılır. Aşağıdaki kodda kullanıcı nesnesinin left ve right düğümler için değişken sağlaması gerekiyor.
struct MyDataNode {
MyDataNode* rbe_left;
MyDataNode* rbe_ight;
char rbe_color;
// data to be stored
};
template <class Entry>
class RbTree {
// implementation
};
// usage:
RbTree<MyDataNode> treeInstance;
Map TanımlamaReferans tipler value olarak tanımlanamaz. Aşağıdaki kod derlenmez.
std::map<std::string, Location&> m;
Static İnitializationC++11 ile static initalization yapmak kolay. Eğer C++11 kullanmıyorsak, boost::assign kullanılabilir.
Süslü parantezler içine key,value değerleri yazılır. Şöyle yaparız.
#include <map>
using namespace std;
map<int, char> m = {{1, 'a'}, {3, 'b'}, {5, 'c'}, {7, 'd'}};
Daha karışık tanımlamalarda şöyle yaparız.static std::map<std::string, std::map<std::string, std::string>> MyMap = {
{"AA", {{"busy_timeout", "test"}}},
{"BB", {{"cache_size", "10000"}}}
};
Map'i DolaşmakIterator ve auto ile map'i dolaşmak için şöyle yaparız.
for (auto& x: mymap) {
std::cout << x.first << " => " << x.second << '\n';
}
SıralamaSıralama için Strict Weak Ordering kullanılır.
Default Comparator
map veri yapısına dışarıdan comparator vermezsek std::less()'i kullanır.
template<
class Key,
class T,
class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<const Key, T> >
> class map;
Dolayısıyla map küçükten büyüğe doğru sıralama yapar. Kendi comparator'umuzu tanımlamak istersek 3 seçeneğinimiz var. Başka bir STL Comparator tanımlamak, Functor kullanmak, Function Pointer kullanmak.< operator tanımlamak
Bir çok primitif tipin ve STL sınıfının < operatörü var. Eğer kendi key sınıfımıza da tanımlamak istersek ya global bir < operatörü tanımlarız ya da member operator yaparız.
bool operator<(const Box &left, const Box &right)
{
return (left.i < right.i);
}
STL Comparator TanımlamakBüyükten küçüğe doğru sıralama yapması için 3. parametreye sıralama algoritması verilebilir. Örnekte std::greater veriliyor.
std::map<double, std::deque<Object>, std::greater<double>> m;
Neden std::greater nesnesini yaratmıyoruz ? Örnekte map template'a compare işlemi için verilen std::greater, default constructor içindeexplicit map (const key_compare& comp = key_compare().....);
şeklinde yaratıldığı için çalışıyor.
Functor Tanımlamak
Örnek'te functor tanımlanıyor ve yine STL örneğinde olduğu gibi functor nesnesini yaratmamız gerekmiyor. Burada dikkat edilmesi gereken nokta, functor'ın operatörü const olmalı. Yani verilen key'leri değiştirmeyeceğini garanti etmeli!
#include<map>
#include<string>
template<typename KeyType>
struct ReverseSort {
bool operator() (const KeyType& key1, const KeyType& key2) const {
return (key1 > key2);
}
};
int main() {
using namespace std;
map<int, string> map1;
map<int, string, ReverseSort<int> > map2 (map1.cbegin(), map1.cend());
return 0;
}
Function Pointer Tanımlamak
Function Pointer kullanmak diğer seçenekler arasında en zor olanı. Kendi function pointer'ımızı kullanmak istesersek, aşağıdaki Case Insensitive örneğinde olduğu gibi function pointer imzasını template içinde tanımlamak ve constructor metoduna da function metodunu geçmek gerekirdi.
Case Insensitive Key
Bazen map'in anahtar alanı string ise ve örnekteki gibi case insensitive anahtar kullanmak istiyorsak
std::map<std::string,
std::string,
bool(*)(const std::string&, const std::string&)>
my_map(case_insensitive_string_cmp);
my_map["Helloooo"] = "foo";
my_map["HELLOOOO"] = "bar";
std::cout << my_map["hellOOOO"] << std::endl;
Case insensitive karşılaştırma ise aşağıda.
at metodu
#include <cctype>
#include <algorithm>
#include <string>
bool case_insensitive_cmp(char lhs, char rhs)
{
return std::toupper(lhs) < std::toupper(rhs);
}
bool case_insensitive_string_cmp(const std::string& lhs,
const std::string& rhs)
{
return std::lexicographical_compare(lhs.begin(),
lhs.end(),
rhs.begin(),
rhs.end(),
case_insensitive_cmp);
}
Map'in Metodlarıat metodu
at metodu yazısına taşıdım
emplace metodu
Açıklaması şöyle
C++11 ile gelen bu metod ile map'in içine direkt nesne yerleştirilebilir.
Bu yüzden aşağıdaki kodda iki defa 1 çıktısını görürüz.
Açıklaması şöyle
Yani anahtar değer mevcutsa ekleme işlemi gerçekleşmez.Inserts a value_type object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t.
The insertion only takes place if no other element in the container has a key equivalent to the one being emplaced (keys in a map container are unique).
std::map<int,std::string> m;
std::string val {"hello"};
m.emplace(1, val);
emplace metodu arama yapmadan önce value nesnesini içinde oluşturur. Aslında standartta böyle bir kural yok ancak çoğu kütüphane böyle çalışıyor. C++17 ile try_emplace metodu ile nasıl çalışması gerektiği de ortaya konacak.Bu yüzden aşağıdaki kodda iki defa 1 çıktısını görürüz.
class HugeConstructor
{
public:
HugeConstructor(int n)
{
std::cout << n << std::endl;
}
};
int main()
{
std::map<int, HugeConstructor> m;
m.emplace(1, 10);
m.emplace(1, 10);
return 0;
}
Eğer key değeri zaten mevcutsa aşağıdaki kod leak verebilir.struct X {};
std::map<int, std::unique_ptr<X> > map;
void f(int x) {
map.emplace(x, new X);
}
erase metodu erase metodu yazısına taşıdım.
extract metodu - C++17
Node handle denen bir kavram geldi. Böylece map içindeki bir girdiyi bir başka veri yapısına taşımak mümkün. Benzer bir metod sd::set içinde de mevcut.
Örnek
Şöyle yaparız.
insert metodu - pair
make_pair ile kullanılır.
İki map'i şöyle birleştiririz.
Output: 1:1 2:1 3:1 3:2 4:1 4:2 5:1 5:2 6:2 7:2
Örnek
Şöyle yaparız.
std::vector<std::pair<std::string, double>> v;
std::map<std::string, double> m{{"aLongString", 1.0},
{"anotherLongString", 2.0}};
auto extracted_value = m.extract("aLongString");
v.emplace_back(std::make_pair(std::move(extracted_value.key()),
std::move(extracted_value.mapped())));
extracted_value = m.extract("anotherLongString");
v.emplace_back(std::make_pair(std::move(extracted_value.key()),
std::move(extracted_value.mapped())));
find metodu
find metodu yazısına taşıdım.
make_pair ile kullanılır.
my_map.insert(std::make_pair(0, std::ref(a)));
İşlemin başarılı olup olmadığıno kontrol etmek için şöyle yaparız.std::map<int, char> m;
auto ret = m.insert(std::make_pair(1, 'A'));
if (ret.second)
std::cout << "it worked" << std::endl;
İşlem başarısız ise mevcut pair'e iterator döner. Bu iterator kullanılabilir. Şöyle yaparız.std::map<int, char> m;
auto ret = m.insert(std::make_pair(1, 'A'));
if (!ret.second)
ret.first->second = 'A'; // assign the value in case it went wrong
insert metodu - iteratorİki map'i şöyle birleştiririz.
#include <map>
#include <iostream>
int main()
{
std::map<int, int> v1 = {{1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}};
std::map<int, int> v2 = {{3, 2}, {4, 2}, {5, 2}, {6, 2}, {7, 2}};
std::multimap<int, int> dest1 {v1.begin(), v1.end()};
dest1.insert(v2.begin(), v2.end());
for (const auto &i : dest1) {
std::cout << i.first << ':' << i.second << ' ';
}
std::cout << '\n';
}
Çıktı olarak şunu alırız.Output: 1:1 2:1 3:1 3:2 4:1 4:2 5:1 5:2 6:2 7:2
Sonuç olarak bir pair döner. Bu nesnenin second alanı çağrının başarılı olup olmadığını belirtir.
Key değeri yoksa yaratır, varsa yeni değeri atar, Şöyle yaparız.
Verilen değerden >= olan ilk değeri döndürür. Map veri yapısı daha verimli olmak için std::lower_bound()'dan farklı olarak kendisi içinde aynı metodu tanımlamış.
Aynı şeyi şöyle de yapabiliriz.
Eskiden C++ standarsında list,map gibi veriyapıları için "should be constant time" cümlesi kullanıldığı için bazı C++ kütüphanelerinde komik bir şekilde size() metodu O(n) çalışıyordu. Hatta bir projede bunu bilmediğimiz için çok ciddi performans problemi de yaşamıştık. C++11 ile bu cümle "shall be constant time" olarak değiştirildi. Artık size() metodo O(1)'de çalışıyor.
try_emplace() yerine daha farklı bir şey olan insert_or_asssign() kullanılabilir. Şöyle yaparız.
[] operator
auto result = map.insert(make_pair(...,...));
if (result.second) {
// insertion succeeded
}
insert_or_assign metoduKey değeri yoksa yaratır, varsa yeni değeri atar, Şöyle yaparız.
std::map<int, ValueClass> value_classes;
value_classes.insert_or_assign(std::make_pair(0, ValueClass());
lower_boundVerilen değerden >= olan ilk değeri döndürür. Map veri yapısı daha verimli olmak için std::lower_bound()'dan farklı olarak kendisi içinde aynı metodu tanımlamış.
merge metodu
Şöyle yaparız
Metodun için şöyleŞöyle yaparız
template<typename K, typename V, typename F>
void mergeWithConflicts(std::map<K, V>& base, std::map<K, V> toMerge, F combine) {
base.merge(toMerge);
// All that's left in toMerge is conflicting keys
for (const auto& [k, v] : toMerge) {
base[k] = combine(base[k], toMerge[k]);
}
}
rbeginreverse_iterator rbegin()
{
return reverse_iterator(end());
}
En sonuncu veriye şöyle erişilir.auto& myKey = myMap.rbegin()->first;
auto& myKey = std::prev(myMap.end())->first;
size
try_emplace metodu
Açıklaması şöyle. Eğer key değeri varsa move işlemini yapmaz.Unlike insert or emplace, these functions do not move from rvalue arguments if the insertion does not happen, which makes it easy to manipulate maps whose values are move-only types, such as std::map<std::string,std::unique_ptr<foo>>. In addition, try_emplace treats the key and the arguments to the mapped_type separately, unlike emplace, which requires the arguments to construct a value_type (that is, a std::pair)C++17 ile geliyor. Eğer key değeri varsa parametreyi dikkate almaz ve mevcut value değerini döner. Eğer key değeri yoksa value nesnesini yaratır. Bu metodun çıkış noktası kafa karışıklığını gidermek. Karışıklışı gösteren bir örnek şöyle.
std::map<std::string, std::unique_ptr<Foo>> m;
m["foo"] = nullptr;
auto ptr = std::make_unique_ptr<Foo>;
auto res = m.emplace("foo", std::move(ptr));
assert(ptr); // ??? (may or may not fire)
try_emplace() yerine daha farklı bir şey olan insert_or_asssign() kullanılabilir. Şöyle yaparız.
m.try_emplace(0, std::cref(a));
m.insert_or_assign(0, std::cref(a));
Şu kod ile aynıdır.
Verilen değerden >olan ilk değeri döndürür.
Örnek
Elimizde şöyle bir kod olsun. > koşulunu sağlayan ilk değeri buluruz. Sonra bir geriye giderek <= koşulunu sağlayan ilk değeri buluruz. Java'daki TreeMap.floorKey() ile aynı işlevi gerçekleştirir.
auto it = mymap.find(42); // search for an element with the key 42
bool is_key_in_map = it != mymap.end();
// if the element with the given key exists, then return it, otherwise
// construct it
auto& newvec = is_key_in_map? it->second:
mymap.emplace(42, std::vector<bool>(100, true)).first->second;
Örnek
Şöyle yaparızstruct Test
{
Test(int i, int j){}
};
std::map<int, Test> tmap;
tmap.try_emplace(10, 10, 10);
upper_bound metoduVerilen değerden >olan ilk değeri döndürür.
Örnek
Elimizde şöyle bir kod olsun. > koşulunu sağlayan ilk değeri buluruz. Sonra bir geriye giderek <= koşulunu sağlayan ilk değeri buluruz. Java'daki TreeMap.floorKey() ile aynı işlevi gerçekleştirir.
#include <map>
template<typename Key, typename Value>
const Key& floorKey(const std::map<Key, Value>& input,
const Key& key, const Key& default_value = {})
{
auto it = input.upper_bound(key);
return it == input.begin()
? default_value
: (--it)->first;
}
Şöyle yaparız.std::map<int, int> map =
{
{ 10, 100 },
{ 20, 200 },
{ 30, 300 },
{ 40, 400 },
{ 50, 500 },
{ 60, 600 },
{ 70, 700 },
{ 80, 800 },
};
std::cout << floorKey(map, 5, -1) << '\n'
<< floorKey(map, 9, -1) << '\n'
<< floorKey(map, 10, -1) << '\n'
<< floorKey(map, 11, -1) << '\n'
<< floorKey(map, 90, -1) << '\n';
Çıktı olarak şunu alırız.-1 -1 10 10 80
Map'in Operatörleri[] operator
std::map [] operator yazısına taşıdım