Asocijativni nasuprot sekvencijalnim spremnicima

Asocijativni spremnici spremaju i dohvaćaju elemente prema ključu koji je pridružen elementu i nije nužno aritmetičkog tipa.

  • Sekvencijali spremnici predstavljaju uređene kolekcije. Mjesto na kojem se nalazi neki element u spremniku ovisi o mjestu na kojem je ubacivan u spremnik, a ne o vrijednosti elementa.

  • Asocijativni spremnici predstavljaju sortirane kolekcije kod kojih položaj elementa u spremniku ovisi o njegovoj vrijednosti, a ne o mjestu na kojem je ubacivan. Oni nam dozvoljavaju da efikasno dohvatimo element prema njegovom ključu.

Dva osnovna tipa asocijativnih spremnika su map i set. Map se sastoji od parova "ključ — vrijednost". Set se sastoji samo od ključeva.

Objekti tipa map i set mogu sadržavati najviše jedan element s danim ključem. Ako želimo spremiti više elemenata s istim ključem onda koristimo multimap i multiset.

Tabela 1. Asocijativni spremnici

map

Asocijativno polja

set

Skup

multimap

Asocijativno polja s ponavljanjem ključeva

multiset

Skup s ponavljanjem ključeva

Postoje još i neuređeni (asocijativni) spremnici koji također dozvoljavaju da se element efikasno dohvati prema njegovom ključu. Kod ovih spremnika pozicija elementa u spremniku nije važna i kroz životni vijek spremnika može se mijenjati. Pozicija elementa ne ovisi niti o mjestu ubacivanja niti o vrijednosti elementa. Jedino je važno je li element u spremniku ili nije. Neuređeni spremnici su sljedeći:

Tabela 2. Neuređeni spremnici

unordered_map

Asocijativno polja

unordered_set

Skup

unordered_multimap

Asocijativno polja s ponavljanjem ključeva

unordered_multiset

Skup s ponavljanjem ključeva

std::set i std::multiset

std::set i std::multiset su spremnici koji automatski sortiraju svoje elemente po nekom kriteriju sortiranja. std::set ne dozvoljava postojanje jednakih elemenata dok std::multiset dozvoljava.

Nema metode koje podrazumijevaju poziciju elementa: push_back, push_front, front, back, pop_front i pop_back.

Da bismo koristili std::set ili std::multiset moramo uključiti <set> zaglavlja.

#include <set>

std::set<char> znakovi;

Spremnici std::set i std::multiset su predlošci klasa as tri parametra predloška: tip elementa T, operator uspoređivanja Compare i alokator Allocator. Zadnja dva parametra imaju predodređene vrijednosti tako da najčešće zadajemo samo prvi argument predloška (koji je obavezan).

template <typename T, typename Compare = less<T>, typename Allocator = allocator<T> > class set;
template <typename T, typename Compare = less<T>, typename Allocator = allocator<T> > class multiset;

Konstruktori

Kod konstrukcije zadajemo tip ključa kao parametar predloška.

Tabela 3. Konstruktori za set i multiset
Operacija Značenje

set<K> c

Dodijeljeni konstruktor; kreira prazan set objekt nazvan c s ključem tipa K

set<K> c(op)

Kreira prazan skup s operatorom uspoređivanja op

set<K> c(c2)

Konstruktor kopije. Kreira c kao kopiju od c2. c i c2 moraju imati isti tip ključa

set<K> c = c2

Konstruktor kopije

set<K> c(rv)

Konstruktor kopije premještanjem

set<K> c = rv

Konstruktor kopije premještanjem

set<K> c(b, e)

Kreira c kao kopiju elemenata iz raspona određenog iteratorima b i e. Elementi moraju imati tip koji se dade konvertirati u tip K.

set<K> c(beg,end)

Kreira skup inicijaliziran elementima iz raspona [beg,end)

set<K> c(beg,end,op)

Kreira skup inicijaliziran elementima iz raspona [beg,end) i s operatorom uspoređivanja op

set<K> c(initlist)

Kreira skup inicijaliziran elementima iz inicijalizacijske liste initlist

set<K> c = initlist

Kreira skup inicijaliziran elementima iz inicijalizacijske liste initlist

c.~set()

Destruktor

Spremnik multiset<K> ima iste konstruktore.

#include <set>
#include <string>
#include <vector>

// ...
vector<string> vec{"n", "p", "a", "q", "k", "i"};
multiset<string> st(vec.begin(), vec.end());
 // set sadrži elemente a,i,k,n,p,q
  • Oba spremnika, std::set i std::multiset imaju dva parametra predloška pri čemu drugi ima predodređenu vrijednost. Drugi parametar predloška je operator uspoređivanja i jednak je operatoru < (std::less<K>) ako se ne zada eksplicitno. Ponekad taj parametar želimo zadati eksplicitno. Na primjer,

#include <set>
#include <string>
#include <vector>
#include <functional> // za std::greater

// ...
vector<string> vec{"n", "p", "a", "q", "k", "i"};
set<string, greater<string>> st(vec.begin(), vec.end());
 // set sadrži elemente q,p,n,k,i,a

Predložak klase std::greater<T> je funkcijski objekt definiran u zaglavlju <functional> koji implementira operator > za tip T.

  • Dva konstrutora koji uzimaju operator uspoređivanja op mogu se koristiti samo ako je tip operatora uspoređivanja eksplicitno dan kao parametar predloška i op je tog tipa. Primjer ćemo vidjeti kasnije.

  • Konstruktor kopije kopira sve elemente spremnika u novi spremnik i pored toga radi i kopiju operatora uspoređivanja.

Operator uspoređivanja

Svaki operator uspoređivanja mora biti nerefleksivan, antisimetričan i tranzitivan:

  • Nerefleksivnost: x < x je uvijek laž;

  • Antisimetrija: x < y implicira da nije y < x;

  • Tranzitivnost: x < y i y < z implicira x < z;

Operator koji zadovoljava gornje aksiome naziva se strogim parcijalnim uređajem i prirodno definira relaciju ekvivalencije:

x eq y ako i samo ako je !(x < y) & !(y < x)

Relacija ekvivalencije mora biti refleksivna ( x eq x je uvijek istina), simetrična ( x eq y implicira da nije y eq x) i tranzitivna (x eq y i y eq z implicira x eq z). Refleksivnost i simetrija slijede iz svojstava relacije < dok tranzitivnost postuliramo kao četvrti aksiom:

  • relacija eq definirana pomoću operatora uspoređivanja < mora biti tranzitivna. Tada operacija < predstavlja tzv. strogi slabi uređaj (eng. strict weak ordering).

Napomena
Asocijativni spremnici ne koriste operator == za određivanje jednakosti elementa, već samo operator <, odnosno umjesto jednakosti oni provjeravaju ekvivalenciju!

Nalaženje elemenata

std::set koristimo kada nam je važno efikasno nalaženje elementa u spremniku. Zbog toga što su elementi u spremniku sortirani kompleksnost metoda za nalaženje elemenata je logaritamska.

vector<string> vec{"n", "p", "a", "q", "k", "i", "q"};
set<string> st(vec.begin(), vec.end());

cout << st.count("q") << endl; // vraća 0 ili 1
auto it = st.find("q"); // vraća poziciju elementa ili end()
if(it != st.end())
    cout << *it << endl;

auto it_l = st.lower_bound("k");
auto it_u = st.upper_bound("k");
copy(it_l,it_u, ostream_iterator<string>(cout,",")); // q,
out << endl;

auto it_er = st.equal_range("k");
assert(it_l == it_er.first);
assert(it_u == it_er.second);

Metode članice klase std::set su efikasnije od odgovarajućih algoritama.

Operacija

Značenje

s.count(e)

Vraća broj pojavljivanja elementa e u spremniku s.

s.find(e)

Vraća iterator na element e, ako takav postoji u spremniku, ili vraća end()-iterator.

s.lower_bound(e)

Vraća iterator koji referira na prvi element >= e.

s.upper_bound(e)

Vraća iterator koji referira na prvi element > e.

s.equal_range(e)

Vraća par iteratora od kojih je prvi s.lower_bound(e), a drugi s.upper_bound(e).

  • s.lower_bound(e) == s.upper_bound(e) znači da element nije nađen.

  • Ako je element nađen, onda s.lower_bound(e) pokazuje na njega, a s.upper_bound(e) pokazuje na prvi veći element ili end().

std::multiset zadržava višestruke kopije elemenata.

vector<string> vec{"n", "p", "a", "q", "k", "i", "q"};
multiset<string> mst(vec.begin(), vec.end());  // a,i,k,n,p,q,q,
auto mit_er = mst.equal_range("q");
copy(mit_er.first, mit_er.second,
     ostream_iterator<string>(cout,",")); // q,q
cout << endl;

Ubacivanje elemenata

Ubacivanje u std::set vrši se metodom insert. Metodi insert možemo dati vrijednost ili raspon iteratora koji određuje elemente koje treba ubaciti.

Operacija

Značenje

s.insert(e)

Ubacuje vrijednost e u spremnik. Vraća par iteratora i bool vrijednosti ako je s std::set, odnosno samo iterator na ubačeni element ako je s std::multiset.

s.insert(beg, end)

beg i end su iteratori koji određuju raspon elemenata koje ubacujemo u spremnik. Ne vraća ništa (tipa void).

s.insert(initlist)

Ubacuje kopiju inicijalizacijske liste initlist u spremnik. Ne vraća ništa (tipa void).

  • Za std::set ove funkcije ubacuju element u spremnik samo ako ekvivalentni element već nije u njemu. Za prvu verzija funkcije povratna vrijednost je std::pair<iterator, bool>. Ako je element već u spremniku bool vrijednost je false, a iterator pokazuje na nađeni element, odnosno na element koji je spriječio ubacivanje; ako element nije u spremniku bool vrijednost je true, a iterator pokazuje na novo ubačeni element.

  • Za std::multiset povratna vrijednost prve verzije funkcije insert je iterator na ubačeni element jer će element u svakom slučaju biti ubačen. Pri tome će relativni poredak ekvivalentnih elemenata biti poštovan i novi element će biti ubačen na kraj niza ekvivalentnih elemenata.

// st sadrži a,i,k,n,p,q
list<string> li{"n","m","o"};
st.insert(li.begin(), li.end());  // st sadrži a,i,k,m,n,o,p,q
auto iit = st.insert("o"); // ubacujemo element koji već postoji
assert( *iit.first == "o");
assert(iit.second == false);  // nije došlo do ubacivanja.

Metoda insert u spremniku std::multiset uvijek ubacuje element (jer dozvoljava duplikate) te je stoga povratni tip prve verzije metode samo iterator na ubačeni element.

// mst sadrži a,i,k,n,p,q,q,
mst.insert(li.begin(), li.end()); // mst sadrži a,i,k,m,n,n,o,p,q,q
auto imit = mst.insert("o"); // mst sadrži a,i,k,m,n,n,o,o,p,q,q
cout << "Element o je ubačen na " << distance(mst.begin(), imit) + 1 << ". mjesto."
     << endl;  // Element o je ubačen na 8. mjesto.

Postoji još jedna verzija insert metode koja je ista za std::set i std::multiset.

Operacija

Značenje

s.insert(pos, e)

Ubacuje vrijednost e u spremnik. Koristi iterator pos kao sugestiju gdje početi tražiti poziciju za ubacivanje. Vraća iterator na ubačeni element ili element koji je spriječio ubacivanje.

Ova metoda ima istu signaturu kao i insert metode u sekvencijalnim spremnicima. Ako se unaprijed zna pozicija ubacivanja onada ima konstantnu složenost.

Postoje i odgovarajuće emplace metode:

Operacija

Značenje

s.emplace(args...)

Ubacuje u spremnik element konstruiran pomoću args (argumenti konstruktor). Vraća par iteratora i bool vrijednosti ako je s std::set, odnosno samo iterator na ubačeni element ako je s std::multiset.

c.emplace_hint(pos,args...)

Ubacuje u spremnik element konstruiran pomoću args (argumenti konstruktor). Vraća iterator na ubačeni element ili element koji je spriječio ubacivanje. Iterator pos je sugestija gdje treba tražiti poziciju za ubacivanje.

Brisanje elemenata

s.erase(e)

Briše sve element jednake e iz s. Vraća broj izbrisanih elemenata.

s.erase(p)

Briše element na koji referira iterator p iz s. Vraća sljedeću poziciju.

s.erase(b, e)

Briše sve elemente u rasponu koji određuje par iteratora b i e. Vraća sljedeću poziciju.

s.clear()

Obriši sve elemente.

Na primjer,

// mst = {a,i,k,m,n,n,p,q,q,q}
auto it1 = mst.find("n");
auto it2 = mst.find("q");
auto it3 = mst.erase(it1, it2); // mst = {a,i,k,m,q,q,q}
assert(*it3 == "q");
mst.clear();            // obriši sve elemente
assert(mst.size() == 0);
assert(mst.empty());

Iteratori i ostalo

Spremnici std::set i std::multiset nude iteratore kao i sekvencijalni spremnici s tom razlikom da su iteratori samo dvosmjerni (nisu izravnog pristupa).

Kroz iteratore ne možemo mijenjati elemente jer se promjenom elementa mijenja njegov položaj u spremniku.

Tabela 4. Iteratori
Operacija Značenje

c.begin()

Vraća iterator na poziciju prvog elementa

c.end()

Vraća iterator na poziciju iza zadnjeg elementa

c.cbegin()

Vraća konstantan iterator na poziciju prvog elementa

c.cend()

Vraća konstantan iterator na poziciju iza zadnjeg elementa

c.rbegin()

Vraća reverzni iterator na poziciju prvog elementa reverznog niza

c.rend()

Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

c.crbegin()

Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza

c.crend()

Vraća konstantan reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

Kao i kod sekvencijalnih spremnika imamo standardne nemodificirajuće metode.

Tabela 5. Nemodificirajuće metode
Operacija Značenje

c.empty()

Vraća true ako je lista prazna, a inače false

c.size()

Vraća broj elemenata u listi

c.max_size()

Vraća maksimalni mogući broj elemenata u listi

c1 == c2

Vraća true ako je c1 jednak c2 (zove == na svakom elementu)

c1 != c2

Vraća true ako c1 nije jednak c2 (isto što i !(c1==c2))

Operacije nad čitavim spremnikom podrazumijevaju da su spremnici istog tipa. To znači da imaju iste parametre predloška: tip elementa i tip operatora uspoređivanja.

Tabela 6. Operacije na čitavom spremniku
Operacija Značenje

c = c2

Pridruži sve elemente spremika c2 spremniku c

c = rv

Pridruži premještanjem sve elemente spremika c2 spremniku c

c = initlist

Pridruži sve elemente inicijalizacijske liste initlist spremniku c

c1.swap(c2)

Zamjeni sadržaj spremnika c1 i c2

swap(c1,c2)

Zamjeni sadržaj spremnika c1 i c2

Operator uspoređivanja je dio spremnika i možemo ga dohvatiti pomoću dvije metode:

Operacija Značenje

c.key_comp()

Vraća objekt koji predstavlja operator uspoređivanja spremnika.

c.value_comp()

Ima posve isti efekt kao i key_comp()

std::map i std::multimap

std::map<Key,T> i std::multimap<Key,T> su kolekcije uređenih parova (ključ, vrijednost). Ključ služi za identifikaciju i dohvat elementa, a vrijednost je vrijednost elementa.

  • Elementi u std::map i std::multimap su sortirani prema ključu.

  • Predodređeni kriterij sortiranja je std::less<Key> no moguće ga je zadati i eksplicitno. Kriterij uspoređivanja mora zadovoljavati uvjete strogog slabog uređaja (eng. strict weak ordering).

  • std::map ne dozvoljava postojanje više parova s istim ključem u spremniku, dok std::multimap dozvoljava.

  • Spremnici std::map i std::multimap su (najčešće) implementirani kao balansirana binarna stabla.

  • Nema metode koje podrazumijevaju poziciju elementa: push_back, push_front, front, back, pop_front i pop_back.

Spremnik std::map<Key, Value> jes sličan spremniku std::set koji drži tip std::pair<const Key, Value>, a sortiranje se vrši prema ključu (koji je zato konstantan).

Spremnici std::map i std::multimap su predlošci klasa as četiri parametra predloška: tip ključa Key, tip vrijednosti T, operator uspoređivanja Compare i alokator Allocator. Zadnja dva parametra imaju predodređene vrijednosti tako da najčešće zadajemo samo prva dva argumenta predloška (koji su obavezni).

template <typename Key, typename T, typename Compare = less<Key>,
                                    typename Allocator = allocator<pair<const Key,T> > > class map;
template <typename Key, typename T, typename Compare = less<Key>,
                                    typename Allocator = allocator<pair<const Key,T> > > class multimap;

Konstrukcija std::map objekta

Da bismo koristili std::map ili std::multimap moramo uključiti <map> zaglavlja. std::map i std::multimap su predlošci klasa čiji je prvi parametar tip ključa, a drugi tip vrijednosti:

#include <map>

std::map<char, int> kod; // ključ je tipa char, vrijednost je tipa int.

Načini konstruiranja spremnika dani su u sljedećoj tabeli:

Operacija

Značenje

map<Key, Value> m;

Dodijeljeni konstruktor; kreira prazan map objekt nazvan m s ključem tipa const k i vrijednosti tipa v.

map<K> m(op)

Kreira prazan skup s operatorom uspoređivanja op

map<Key, Value> m(m2);

Konstruktor kopije. Kreira m kao kopiju od m2. m i m2 moraju imati isti tip ključa i vrijednosti.

map<Key, Value> m = m2

Konstruktor kopije

map<Key, Value> m(rv)

Konstruktor kopije premještanjem

map<Key, Value> m = rv

Konstruktor kopije premještanjem

map<Key, Value> m(b, e);

Kreira m kao kopiju elemenata iz raspona određenog iteratorima b i e. Elementi moraju imati tip koji se dade konvertirati u std::pair<const Key, Value>.

map<Key, Value> m(b, e, op)

Kreira m s operatorom uspoređivanja op kao kopiju elemenata iz raspona određenog iteratorima b i e.

map<Key, Value> m(initlist)

Kreira m inicijaliziran elementima iz inicijalizacijske liste initlist

map<Key, Value> m = initlist

Kreira m inicijaliziran elementima iz inicijalizacijske liste initlist

m.~set()

Destruktor

  • Spremnik std::multimap ima iste konstruktore.

  • Tipovi ključa i vrijednosti moraju se moći kopirati ili premještati.

  • Tip ključa mora biti takav da je na njemu dobro definiran operator <. Taj se operator koristi za uspoređivanje elemenata i mora imati svojstva strogog slabog uređaja.

  • Korisnik ima mogućnost definirati svoj operator uspoređivanja.

U sljedećem primjeru std::map konstruiramo iz inicijalizacijske liste:

#include <map>
#include <string>
#include <iostream>
#include <iomanip>  // za manupulator setprecision

// pod windowsima trebamo ovaj "define" za matematičke konstante
#define _USE_MATH_DEFINES
#include <cmath>

using namespace std;

int main()
{
  map<string, double> konstante{
              {"sqrt2", M_SQRT2}, {"pi", M_PI}, {"e", M_E}, {"log_2e", M_LOG2E},
              { "log_10e", M_LOG10E}, {"ln2", M_LN2}, {"ln10", M_LN10}
                                     };
  for(auto x : konstante)
        cout << setw(10) << x.first << " = " << setprecision(16) << x.second << "\n";

    // ...
  return 0;
}

Kod daje ispis:

         e = 2.718281828459045
      ln10 = 2.302585092994046
       ln2 = 0.6931471805599453
   log_10e = 0.4342944819032518
    log_2e = 1.442695040888963
        pi = 3.141592653589793
     sqrt2 = 1.414213562373095

Nalaženje elementa

Za nalaženje elementa imamo iste operacije kao i kod set i multiset spremnika. Pretraživanje uvijek ide prema ključu.

m.count(key)

Vraća broj pojavljivanja ključa key u spremniku m.

m.find(key)

Vraća iterator na element indeksiran sa key, ako takav postoji, ili vraća end iterator.

m.lower_bound(key)

Vraća iterator koji referira na prvi element čiji ključ nije manji od key.

m.upper_bound(key)

Vraća iterator koji referira na prvi element čiji je ključ veći od key.

m.equal_range(key)

Vraća par iteratora od kojih je prvi ekvivalentan s m.lower_bound(key), a drugi s m.upper_bound(key).

map i multimap iteratori pokazuju na tip std::pair<Key, Value>.

cout << konstante.size() << endl;      // broj elemenata
cout << konstante.count("pi") << endl; //  broj pojavljivanja ključa "pi" (kod map 0 ili 1)

auto it = konstante.find("e");   // nađi element e
assert(it->first == "e");
cout << "e = " << it->second << endl; // e = 2.718281828459045

auto it_l = konstante.lower_bound("ln2");
auto it_u = konstante.upper_bound("pi");
for(auto i = it_l; i != it_u; ++i)
        cout << setw(10) << i->first << " = " << i->second << "\n";

Zadnja petlja ispisuje:

       ln2 = 0.6931471805599453
   log_10e = 0.4342944819032518
    log_2e = 1.442695040888963
        pi = 3.141592653589793
  • Operacija lower_bound(key) daje iterator koji referira na prvo pojavljivanje ključa key u spremniku, ako element s tim ključem postoji. Operacija upper_bound(key) vraća iterator na prvi element iza posljednjeg elementa s ključem key. U toj situaciji par [lower_bound(key), upper_bound(key)) predstavlja raspon elementa s ključem key.

  • Kada element s ključem key ne postoji u spremniku tada lower_bound(key) i upper_bound(key) vraćaju iterator koji referira na isti element (ili end-iterator). Taj element predstavlja mjesto na koje možemo ubaciti element s ključem key a da ne narušimo poredak elemenata u spremniku.

Iteratori

Spremnici std::map i std::multimap nude iteratore kao i sekvencijalni spremnici s tom razlikom da su iteratori samo dvosmjerni (nisu izravnog pristupa).

Kroz iteratore ne možemo mijenjati ključ elementa jer se promjenom ključa mijenja njegov položaj u spremniku. Iteratori iteriraju po elementima tipa std::pair<const Key, Value>.

Tabela 7. Iteratori
Operacija Značenje

c.begin()

Vraća iterator na poziciju prvog elementa

c.end()

Vraća iterator na poziciju iza zadnjeg elementa

c.cbegin()

Vraća konstantan iterator na poziciju prvog elementa

c.cend()

Vraća konstantan iterator na poziciju iza zadnjeg elementa

c.rbegin()

Vraća reverzni iterator na poziciju prvog elementa reverznog niza

c.rend()

Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

c.crbegin()

Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza

c.crend()

Vraća konstantan reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

Za iteriranje kroz spremnik možemo koristiti rab+nge-for petlju:

std::map<std::string, int> members{{"A.B", 3}, {"R.E", 11}, {"K.M",8}};

for(auto x : members){
     std::cout << x.first << " : " << x.second << "\n";
   //x.first = "F.Z";  -- Greška, ključ je konstantan
     x.second =0; // vrijednost možemo mijenjati
                  // (ali ovdje mijenjamo samo kopiju vrijednosti)
}

Ekvivalentan ispis pomoću iteratora:

for(auto it = members.begin(); it != members.end(); ++it)
    std::cout << it->first << " : " << it->second << "\n";

Kada koristimo algoritme s lambdama tip elementa u std::map ili std::multimap trebamo eksplicitno navesti.

std::for_each(members.begin(), members.end(),
            [](std::pair<const std::string, int> & u){ u.second += 10;});

Tip std::pair<const std::string, int> možemo dobiti kao

std::map<std::string, int>::value_type

i kao

decltype(members)::value_type

Ubacivanje elemenata

Elementi se ubacuju u map pomoću metode insert. Element koji se ubacuje mora biti tipa std::pair<Key,Val>, gdje je Key tip ključa, a Val tip vrijednosti. Analogno, metoda koja uzima iteratore traži da on referiraju na odgovarajuće parove.

m.insert(e)

Ubacuje vrijednost e u spremnik. Vraća par iteratora i bool vrijednosti ako je m tipa std::map, odnosno samo iterator na ubačeni element ako je m tipa std::multimap.

m.insert(beg, end)

beg i end su iteratori koji određuju raspon elemenata koje ubacujemo u m. Vraća void.

m.insert(initlist)

Ubacuje kopiju inicijalizacijske liste initlist u spremnik. Ne vraća ništa (tipa void).

  • Kod ubacivanja elementa e u map, insert ubacuje element u spremnika ako pripadni ključ (e.first) nije u m. U suprotnom m ostaje neizmjenjen. Povratna vrijednost je par (std::pair) čiji je prvi element iterator koji referira na element s ključem e.first, a drugi je bool varijabla koja indicira je li element ubačen ili nije.

  • Kod ubacivanja elementa e u multimap uvijek dolazi do ubacivanja (jer se dozvoljavaju višestruki elementi) i povratna vrijednost je iterator na ubačenu vrijednost.

auto iit = konstante.insert(make_pair("1/pi ", M_1_PI)); // make_pair napravi std::pair<,>
assert(iit.second == true);
for(auto x : konstante)
        cout << setw(10) << x.first << " = "  << x.second << "\n";

Ispis:

     1/pi  = 0.3183098861837907
         e = 2.718281828459045
      ln10 = 2.302585092994046
       ln2 = 0.6931471805599453
   log_10e = 0.4342944819032518
    log_2e = 1.442695040888963
        pi = 3.141592653589793
     sqrt2 = 1.414213562373095

Postoji još jedna verzija insert metode koja je ista za std::map i std::multimap.

Operacija

Značenje

m.insert(pos, e)

Ubacuje vrijednost e u spremnik. Koristi iterator pos kao sugestiju gdje početi tražiti poziciju za ubacivanje. Vraća iterator na ubačeni element ili element koji je spriječio ubacivanje.

Ova metoda ima istu signaturu kao i insert metode u sekvencijalnim spremnicima i odgovarajuća metoda u std::set i std::multiset. Ako se unaprijed zna pozicija ubacivanja onada ima konstantnu složenost.

auto init = members.insert(members.begin(),  // hint
                          decltype(members)::value_type{"A.U",11});
assert(init == members.find("A.U"));

Postoje i odgovarajuće emplace metode posve analogne kao i kos set i multiset spremnika:

Operacija

Značenje

m.emplace(args...)

Ubacuje u spremnik element konstruiran pomoću args (argumenti konstruktor). Vraća par iteratora i bool vrijednosti ako je m tipa std::map, odnosno samo iterator na ubačeni element ako je m tipa std::multimap.

m.emplace_hint(pos,args...)

Ubacuje u spremnik element konstruiran pomoću args (argumenti konstruktor). Vraća iterator na ubačeni element ili element koji je spriječio ubacivanje. Iterator pos je sugestija gdje treba tražiti poziciju za ubacivanje.

Kao i kod analognog konstruktora tipa std::pair dužni smo koristiti oznaku std::piecewise_construct kako bi predali argumente konstruktoru ključa i vrijednosti. Argumenti konstruktora se predaju pakirani u std::tuple te stoga koristimo funkciju std::make_tuple.

std::map<std::string, std::complex<double>> nums;

auto rez= nums.emplace(std::piecewise_construct,
                       std::make_tuple("Im"),    // argumenti konstruktora prvog tipa
                       std::make_tuple(0.0,1.0)  // argumenti konstruktora drugog tipa
                      );
assert(rez.second == true);

decltype(nums)::iterator it  = nums.begin();
decltype(nums)::iterator end = nums.end();


for(; it != end; ++it) std::cout << "(" << it->first   // ključ
                                 << "," << it->second  // vrijednost
                                 << ")" << std::endl;

Brisanje elementa

Brisanje elementa se vrši pomoću istih metoda kao kos set i multiset spremnika.

m.erase(key)

Izbriši element s ključem key iz m. Vraća broj izbrisanih elemenata.

m.erase(p)

Briše element na koji referira iterator p. Vraća iterator na sljedeći element.

m.erase(b, e)

Briše elemente u rasponu koji određuju iteratori b i e. Vraća iterator na sljedeći element.

U prvoj verziji multimap briše sve elemente danog ključa.

U zadnjoj verziji funkcije erase raspon koji određuju iteratori mora biti ispravan (odgovornost programeta), a može biti i prazan: tada funkcija ne radi ništa.

int ni = konstante.erase("ln2");
assert(ni == 1);
for(auto x : konstante)
        cout << setw(10) << x.first << " = "  << x.second << "\n";

Ispis:

     1/pi  = 0.3183098861837907
         e = 2.718281828459045
      ln10 = 2.302585092994046
   log_10e = 0.4342944819032518
    log_2e = 1.442695040888963
        pi = 3.141592653589793
     sqrt2 = 1.414213562373095

Kada brišemo element u petlji po svim elementima moramo brinuti o obezvrijeđivanju iteratora:

std::map<std::string, int> members{{"A.B", 3}, {"R.E", 11}, {"K.M",8}};

for(auto it = members.begin(); it != members.end(); ++it){
    if(it->second == 11)
        members.erase(it);  // neispravno
}

Ispravan kod bi bio:

std::map<std::string, int> members{{"A.B", 3}, {"R.E", 11}, {"K.M",8}};

for(auto it = members.begin(); it != members.end(); /* prazno */ ){
    if(it->second == 11)
        it = members.erase(it);  // erase vraća pokazivač na sljedeći element
    else
        ++it;
}

map kao asocijativno polje

Asocijativni spremnici ne nude direktan pristup elementima već moramo koristiti iteratore. Spremnik std::map je s te strane iznimka. On nudi operator uglatih zagrada kao i std::vector ili std::deque, ali s ponašanjem koje je bitno različito.

m[key]=val

Vraća referencu na vrijednost koja je pridružena ključu key. Ako key nije prisutan u spremniku ubacuje u njega par (key,val).

m.at(key)

Vraća referencu na vrijednost koja je pridružena ključu key i izbacuje izuzetak std::out_of_range ako element ne postoji.

Sljedeći primjer pokazuje prednosti i nedostatke indeksiranja:

using namespace std;

std::map<std::string,double> data;// prazan map

data["tlak"] = 121.5; // ubaci tlak

//"poroznost" još nije ubačena u map
cout << data["poroznost"] << endl;// ubacuje par ("poroznost",0.0) što vjerojatno nismo htjeli!

Naredba data["tlak"] = 121.5; izvršava se na sljedeći način:

  • Traži se element "tlak" u spremniku. Ako postoji vraća se referenca na pripadnu vrijednost.

  • Ako "tlak" ne postoji u spremniku kreira se par ("tlak", 0.0) i ubacuje se u spremnik. Zatim se vraća referenca na novu vrijednost.

  • Vrši se pridruživanje vrijednosti 121.5 kroz referencu.

Lošu stranu indeksiranja ilustrira naredba cout << data["poroznost"] << endl; kojom nenamjerno ubacujemo element u spremnik (pod pretpostavkom da "poroznost" još nije u spremniku).

Neuređeni asocijativni spremnici

Neuređeni asocijativni spremnici su bazirani na modelu hash tablice. Pomoću hash funkcije ključ se transformira u cjelobrojni indeks koji pokazuje na mjesto u tablici gdje treba smjestiti element. Premda C++ standard ne propisuje detalje implementacije hash tablice, specifikacija sučelja neuređenih spremnika je bazirana na otvorenom haširanju koje pretpostavlja da se u slučaju kolizija elementi s istim indeksom spremaju u obliku povezane liste.

Neuređeni spremnici u standardnoj biblioteci su sljedeći:

Tabela 8. Neuređeni spremnici

unordered_set

Skup

unordered_map

Asocijativno polja

unordered_multiset

Skup s ponavljanjem ključeva

unordered_multimap

Asocijativno polja s ponavljanjem ključeva

Za korištenje ovih spremnika treba uključiti zaglavlja <unordered_set> i/ili <unordered_map>.

  • unordered_set i unordered_multiset čuvaju jednu vrijednost (ključ) dok unordered_map i unordered_multimap čuvaju parove ključ/vrijednost.

  • unordered_set i unordered_map ne dozvoljavaju višestruke elemente s istim ključem, dok unordered_multimap i unordered_multiset dozvoljavaju.

Spremnici unordered_set i unordered_multiset su predlošci klasa sa četiri parametra predloška, od kojih tri imaju predodređene vrijednosti:

template <typename T, typename Hash = hash<T>,
                      typename EqPred = equal_to<T>,
                      typename Allocator = allocator<T> >
class unordered_set;

Spremnici unordered_map i unordered_multimap su predlošci klasa sa pet parametra predloška, od kojih tri imaju predodređene vrijednosti:

template <typename Key, typename T, typename Hash = hash<Key>,
                                    typename EqPred = equal_to<Key>,
                                    typename Allocator = allocator<pair<const Key, T> > >
class unordered_map;

Predodređene vrijednosti imaju hash funkcija, operator uspoređivanja i alokator.

Korisnik može kontrolirati sljedeće implementacijske aspekte neuređenih spremnika:

  • Minimalni broj pretinaca (buckets);

  • Hash funkciju;

  • Operator uspoređivanja;

  • Maximalno opterećenje (maximum load factor);

  • Moguće je izvršiti rehaširanje.

Konstruktori

Operacija

Značenje

Unord c

Konstruktor bez argumenata konstruira prazan spremnik bez elemenata

Unord c(bnum)

Kreira prazan spremnik koji koristi minimalno bnum pretinaca

Unord c(bnum,hf)

Kreira prazan spremnik koji koristi minimalno bnum pretinaca i koristi hash funkciju hf

Unord c(bnum,hf,cmp)

Kreira prazan spremnik koji koristi minimalno bnum pretinaca, koristi hash funkciju hf i predikat cmp za identifikaciju jednakih elemenata

Unord c(c2)

Konstruktor kopije

Unord c = c2

Konstruktor kopije

Unord c(rv)

Konstruktor kopije premještanjem

Unord c = rv

Konstruktor kopije premještanjem

Unord c(beg,end)

Kreira spremnik inicijaliziran elementima iz raspona [beg,end)

Unord c(beg,end,bnum)

Kreira spremnik inicijaliziran elementima iz raspona [beg,end) koji koristi barem bnum pretinaca

Unord c(beg,end,bnum,hf)

Kreira spremnik inicijaliziran elementima iz raspona [beg,end) koji koristi barem bnum pretinaca i hash funkciju hf

Unord c(beg,end,bnum,hf,cmp)

Kreira spremnik inicijaliziran elementima iz raspona [beg,end) koji koristi barem bnum pretinaca, hash funkciju hf i predikat cmp za identifikaciju jednakih elemenata

Unord c(initlist)

Kreira spremnik inicijaliziran elementima iz inicijalizacijske liste initlist

Unord c = initlist

Kreira spremnik inicijaliziran elementima iz inicijalizacijske liste initlist

c.~Unord()

Destruktor

U ovoj tabeli Unord može bito

unordered_set<Elem>, unordered_set<Elem,Hash>, unordered_set<Elem,Hash,Cmp>,

unordered_multiset<Elem>, unordered_multiset<Elem,Hash>, unordered_multiset<Elem,Hash,Cmp>,

unordered_map<Key,T>, unordered_map<Key,T,Hash>, unordered_map<Key,T,Hash,Cmp>,

unordered_multimap<Key,T>, unordered_multimap<Key,T,Hash>, unordered_multimap<Key,T,Hash,Cmp>.

Na primjer,

#include <iostream>
#include <string>
#include <unordered_set>
#include <unordered_map>

std::unordered_set<std::string> s2{"UX","MSDOS","WINDOWS","OSX"};

for(auto x : s2)
    std::cout << x << ",";
std::cout << "\n";

std::unordered_map<std::string, int> m1{{"UX",1},{"MSDOS",2},
                                        {"WINDOWS",3},{"OSX",4}};

for(auto x : m1)
     std::cout << "[" <<x.first << "," << x.second << "]\n";

Nalaženje elemenata

Neuređeni spremnici nemaju metode lower_bound() i upper_bound() jer ne koriste operator <, ali zato imaju equal_range() koji je implementiran pomoću operatora ==.

Općenito, neuređeni spremnici se koriste zbog brzog nalaženja elementa (ukoliko je hash funkcija kvalitetna). To su sljedeće funkcije:

c.count(key)

Vraća broj pojavljivanja ključa key u spremniku m.

c.find(key)

Vraća iterator na element indeksiran sa key, ako takav postoji, ili vraća end iterator.

c.equal_range(key)

Vraća par iteratora koji predstavljaju raspon elemenata s ključem key.

Prethodni primjer možemo nastaviti na sljedeći način:

std::cout << "#OSX = " << m1.count("OSX") << std::endl;
auto it = m1.find("MSDOS");
if(it != m1.end()){
    std::cout << "MSDOS = " << it->second << "\n";
}
else{
    std::cout << "MSDOS not found!\n";
}

auto range = m1.equal_range("OSX");
std::cout << "range =";
for(auto itt= range.first; itt != range.second; ++itt)
    std::cout << " {" << itt->first << "," << itt->second << "} ";
std::cout << "\n";

Iteratori

Spremnici std::unordered_map i std::unordered_multimap nude iteratore kao i uređeni asocijativni spremnici s tom razlikom da su iteratori samo jednosmjerni.

Kroz iteratore ne možemo mijenjati ključ elementa jer se promjenom ključa mijenja njegov položaj u spremniku. Iteratori iteriraju po elementima tipa std::pair<const Key, Value>.

Tabela 9. Iteratori
Operacija Značenje

c.begin()

Vraća iterator na poziciju prvog elementa

c.end()

Vraća iterator na poziciju iza zadnjeg elementa

c.cbegin()

Vraća konstantan iterator na poziciju prvog elementa

c.cend()

Vraća konstantan iterator na poziciju iza zadnjeg elementa

Za iteriranje kroz spremnik možemo koristiti range-for petlju ili direktno iteratore:

std::unordered_multimap<std::string, int> m1{{"OSX",4},{"UX",1},{"MSDOS",2},
                                             {"WINDOWS",3},{"OSX",4}};

for(auto x : m1)
    std::cout << "[" <<x.first << "," << x.second << "]\n";

for(auto it = m1.begin(); it != m1.end(); ++it)
    std::cout <<  "[" << it->first << "," << it->second << "]\n";

Pri korištenju algoritama i lambdi moramo točno deklarirati tip elementa u spremniku. Na primjer,

std::for_each(m1.begin(), m1.end(), [](std::pair<const std::string, int> & elem)
                                         {elem.second+=10;});

jer

std::for_each(m1.begin(), m1.end(), [](std::pair<std::string, int> & elem)
                                         {elem.second+=10;});

daje grešku pri kompilaciji (zbog nekonstantnog ključa). Kod se može pojednostaviti korištenjem auto deklaracije (uvedene u C++14):

std::for_each(m1.begin(), m1.end(), [](auto & elem){elem.second+=10;});

Ubacivanje elemenata

Sučelje neuređenih spremnika je sasvim isto kao i sučelje uređenih. Elementi se ubacuju u spremnik pomoću metode insert. Spremnici unordered_set i unordered_multiset pamte samo ključ te se on ubacuje u spremnik. S druge strane, unordered_map i unordered_multimap čuvaju par ključ/vrijednost tako da element koji se ubacuje mora biti tipa std::pair<Key,Val>, gdje je Key tip ključa, a Val tip vrijednosti. Analogno, insert metoda koja uzima iteratore traži da oni referiraju na odgovarajuće tipove.

c.insert(e)

Ubacuje vrijednost e u spremnik. Vraća par iteratora i bool vrijednosti ako je c tipa set ili map, odnosno samo iterator na ubačeni element ako je c tipa multiset ili multimap.

c.insert(beg, end)

beg i end su iteratori koji određuju raspon elemenata koje ubacujemo u c. Vraća void.

c.insert(initlist)

Ubacuje kopiju inicijalizacijske liste initlist u spremnik. Ne vraća ništa (tipa void).

c.insert(pos, e)

Ubacuje vrijednost e u spremnik. Koristi iterator pos kao sugestiju (hint) gdje početi tražiti poziciju za ubacivanje. Vraća iterator na ubačeni element ili element koji je spriječio ubacivanje.

  • Kod ubacivanja elementa e u unordered_set i unordered_map, metoda insert ubacuje element u spremnika ako pripadni ključ nije prisutan u c. U suprotnom c ostaje neizmjenjen. Povratna vrijednost je par (std::pair) čiji je prvi element iterator koji referira na element s ključem e.first, a drugi je bool varijabla koja indicira je li element ubačen ili nije.

  • Kod ubacivanja elementa e u multiset i multimap uvijek dolazi do ubacivanja (jer se dozvoljavaju višestruki elementi) i povratna vrijednost je iterator na ubačenu vrijednost.

  • Zadnja metoda ima istu signaturu kao i insert metode u sekvencijalnim spremnicima i odgovarajuća metoda u uređenim asocijativnim spremnicima. Ako se unaprijed zna pozicija ubacivanja onada ima konstantnu složenost.

Postoje i odgovarajuće emplace metode posve analogne kao i kod uređenih asocijativnih spremnika:

Operacija

Značenje

c.emplace(args...)

Ubacuje u spremnik element konstruiran pomoću args (argumenti konstruktor). Vraća par iteratora i bool vrijednosti ako je c tipa std::map, odnosno samo iterator na ubačeni element ako je c tipa std::multimap.

c.emplace_hint(pos,args...)

Ubacuje u spremnik element konstruiran pomoću args (argumenti konstruktor). Vraća iterator na ubačeni element ili element koji je spriječio ubacivanje. Iterator pos je sugestija gdje treba tražiti poziciju za ubacivanje.

Kao i kod analognog konstruktora tipa std::pair dužni smo koristiti oznaku std::piecewise_construct kako bi predali argumente konstruktoru ključa i vrijednosti. Argumenti konstruktora se predaju pakirani u std::tuple te stoga koristimo funkciju std::make_tuple.

Ovdje je nekoliko primjera ubacivanja elemenata u spremnik u kojima nastavljamo primjer s prethodne stranice.

auto insertit = m1.insert({"Linux", 93}); // m1 je unordered_multimap<string, int>
assert(insertit->second == 93);
insertit = m1.insert(std::pair<std::string,int>("Linux", 93)); // eksplicitno
insertit = m1.insert(decltype(m1)::value_type("Linux", 93));   // zadavanje
insertit = m1.insert(std::make_pair("Linux", 93));             // tipa

insertit = m1.emplace(std::piecewise_construct,
                      std::make_tuple("Android"),  // za konstruktor stringa
                      std::make_tuple(17));        // za konstruktor int-a

std::unordered_set<int> s1{1,2,5,7,4,9};
auto inss1 = s1.insert(9);
if(inss1.second)
   std::cout << "9 je ubačen u skup.\n";
else
   std::cout << "9 je već u skupu.\n";

Brisanje elementa

Brisanje elementa se vrši pomoću istih metoda kao kos set i multiset spremnika.

c.erase(key)

Izbriši sve elemente s ključem key iz c. Vraća broj izbrisanih elemenata.

c.erase(p)

Briše element na koji referira iterator p. Vraća iterator na sljedeći element.

c.erase(b, e)

Briše elemente u rasponu koji određuju iteratori b i e. Vraća iterator na sljedeći element.

c.clear()

Briše sve elemente.

U prvoj verziji multimap briše sve elemente danog ključa.

U zadnjoj verziji funkcije erase raspon koji određuju iteratori mora biti ispravan (odgovornost programeta), a može biti i prazan: tada funkcija ne radi ništa.

auto nob = m1.erase("Linux");
std::cout << "Obrisano " << nob << " elemenata.\n"; // Obrisano 4 elemenata.

auto fit = m1.find("OSX");
if(fit != m1.end())
    fit = m1.erase(fit); // Obriši prvi "OSX"
std::cout  << "[" << fit->first << "," << fit->second << "]\n"; // [[OSX,14]

Kod brisanja elementa u petlji po svim elementima moramo brinuti o obezvrijeđivanju iteratora jednako kao i kod uređenih spremnika (vidi primjer tamo).

unordered_map kao asocijativno polje

Asocijativni spremnici ne nude direktan pristup elementima već moramo koristiti iteratore. Spremnik std::unordered_map je s te strane iznimka kao i std::map. On nudi operator uglatih zagrada koji se ponaša kao i isti operator u std::map.

m[key]=val

Vraća referencu na vrijednost koja je pridružena ključu key. Ako key nije prisutan u spremniku ubacuje u njega par (key,val).

m.at(key)

Vraća referencu na vrijednost koja je pridružena ključu key i izbacuje izuzetak std::out_of_range ako element ne postoji.

Sljedeći primjer pokazuje prednosti i nedostatke indeksiranja:

std::unordered_map<std::string, int> m2;
m2["Linux"] = 7;  // Ubačen novi element {"Linux", 7}

std::cout << "m2[Linux] = " << m2["Linux"] << std::endl; // Ispis
std::cout << "m2[UX] = " << m2["UX"] << std::endl;  // Ubačen novi
                                                    // element {"UX",0}
//std::cout << "m2[UX] = " << m2.at("UX") << std::endl; // bolji način

Operacije vezane uz haširanje

Neuređeni spremnici imaju jedan broj metoda za ispitivanje stanja spremnika. Oni mogu vratiti broj pretinaca, trenutno opterećenje, trenutno maksimalno opterećenje itd.

Operacija

Značenje

c.hash_function()

Vraća hash funkciju

c.key_eq()

Vraća predikat ekvivalencije

c.bucket_count()

Vraća trenutni broj pretinaca

c.max_bucket_count()

Vraća maksimalni mogući broj pretinaca

c.load_factor()

Vraća trenutno opterećenje

c.max_load_factor()

Vraća trenutno maksimalni opterećenje

c.max_load_factor(val)

Postavlja maksimalno opterećenje na val

c.rehash(bnum)

Vrši rehaširanje spremnika tako da broj pretinaca bude barem bnum

c.reserve(num)

Vrši rehaširanje spremnika tako da ima prostora za barem num elementa

Možemo k tome postaviti maksimalno opterećenje te izvršiti rehaširanje.

  • Opterećenje je omjer između broja elemenata u spremniku i broja pretinaca.

  • Maksimalni opterećenje je ono pri kojem se povećava broj pretinaca i vrši se rehaširanje. Predodređena vrijenost za maksimalni opterećenje je 1.

  • U odnosu na c.reserve(num) poziv c.rehash(bnum) priprema spremnik za barem bnum*c.max_load_factor() elemenata.

Za ispitivanje strukture spremnika imamo i sljedeće metode:

Operacija

Značenje

c.bucket(val)

Vraća indeks pretinca u koji bi vrijednost val bila spremljena

c.bucket_size(buckidx)

Vraća broj elemenata u pretincu s indeksom buckidx

c.begin(buckidx)

Vraća jednosmjerni iterator koji pokazuje na prvi element u pretincu s indeksom buckidx

c.end(buckidx)

Vraća jednosmjerni iterator koji pokazuje iza zadnjeg elementa u pretincu s indeksom buckidx

c.cbegin(buckidx)

Vraća konstantan jednosmjerni iterator koji pokazuje na prvi element u pretincu s indeksom buckidx

c.cend(buckidx)

Vraća konstantan jednosmjerni iterator koji pokazuje iza zadnjeg elementa u pretincu s indeksom buckidx

Primjer

Ovdje je program koji koristi funkcije za ispitivanje spremnika kako bi ispisao njegovu strukturu.

#include <unordered_map>
#include <iostream>
#include <string>

using namespace std;

int main()
{
    unordered_map<string, int> map{{"OSX",4},{"UX",1},{"MSDOS",2},
                                   {"WINDOWS",3},{"Android",5}};

    cout << "Broj elemenata         = " << map.size() << endl;
    cout << "Broj pretinaca         = " << map.bucket_count()  << endl;
    cout << "Opterećenje            = " << map.load_factor() << endl;
    cout << "Maksimalno opterećenje = " << map.max_load_factor() << endl;
    cout << "Data:\n";

    for(size_t i = 0; i < map.bucket_count(); ++i){
      cout << "b[" << i << "] ";
      for(auto it=map.cbegin(i); it != map.cend(i); ++it)
          cout << "{" << it->first << "," << it->second << "} ";
      cout << "\n";
    }
    return 0;
}

Ispis:

Broj elemenata         = 5
Broj pretinaca         = 7
Opterećenje            = 0.714286
Maksimalno opterećenje = 1
Data:
b[0]
b[1]
b[2] {UX,1}
b[3] {WINDOWS,3}
b[4] {Android,5}
b[5]
b[6] {MSDOS,2} {OSX,4}