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.
|
Asocijativno polja |
|
Skup |
|
Asocijativno polja s ponavljanjem ključeva |
|
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:
|
Asocijativno polja |
|
Skup |
|
Asocijativno polja s ponavljanjem ključeva |
|
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.
Operacija | Značenje |
---|---|
|
Dodijeljeni konstruktor; kreira prazan |
|
Kreira prazan skup s operatorom uspoređivanja |
|
Konstruktor kopije. Kreira |
|
Konstruktor kopije |
|
Konstruktor kopije premještanjem |
|
Konstruktor kopije premještanjem |
|
Kreira |
|
Kreira skup inicijaliziran elementima iz raspona |
|
Kreira skup inicijaliziran elementima iz raspona |
|
Kreira skup inicijaliziran elementima iz inicijalizacijske liste |
|
Kreira skup inicijaliziran elementima iz inicijalizacijske liste |
|
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
istd::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 iop
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 |
|
Vraća broj pojavljivanja elementa |
|
Vraća iterator na element |
|
Vraća iterator koji referira na prvi element >= |
|
Vraća iterator koji referira na prvi element > |
|
Vraća par iteratora od kojih je prvi |
-
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, as.upper_bound(e)
pokazuje na prvi veći element iliend()
.
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 |
|
Ubacuje vrijednost |
|
|
|
Ubacuje kopiju inicijalizacijske liste |
-
Za
std::set
ove funkcije ubacuju element u spremnik samo ako ekvivalentni element već nije u njemu. Za prvu verzija funkcije povratna vrijednost jestd::pair<iterator, bool>
. Ako je element već u spremnikubool
vrijednost jefalse
, aiterator
pokazuje na nađeni element, odnosno na element koji je spriječio ubacivanje; ako element nije u spremnikubool
vrijednost jetrue
, aiterator
pokazuje na novo ubačeni element. -
Za
std::multiset
povratna vrijednost prve verzije funkcijeinsert
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 |
|
Ubacuje vrijednost |
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 |
|
Ubacuje u spremnik element konstruiran pomoću |
|
Ubacuje u spremnik element konstruiran pomoću |
Brisanje elemenata
|
Briše sve element jednake |
|
Briše element na koji referira iterator |
|
Briše sve elemente u rasponu koji određuje par iteratora |
|
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.
Operacija | Značenje |
---|---|
|
Vraća iterator na poziciju prvog elementa |
|
Vraća iterator na poziciju iza zadnjeg elementa |
|
Vraća konstantan iterator na poziciju prvog elementa |
|
Vraća konstantan iterator na poziciju iza zadnjeg elementa |
|
Vraća reverzni iterator na poziciju prvog elementa reverznog niza |
|
Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza |
|
Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza |
|
Vraća konstantan reverzni iterator na poziciju iza zadnjeg elementa reverznog niza |
Kao i kod sekvencijalnih spremnika imamo standardne nemodificirajuće metode.
Operacija | Značenje |
---|---|
|
Vraća |
|
Vraća broj elemenata u listi |
|
Vraća maksimalni mogući broj elemenata u listi |
|
Vraća |
|
Vraća |
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.
Operacija | Značenje |
---|---|
|
Pridruži sve elemente spremika |
|
Pridruži premještanjem sve elemente spremika |
|
Pridruži sve elemente inicijalizacijske liste |
|
Zamjeni sadržaj spremnika |
|
Zamjeni sadržaj spremnika |
Operator uspoređivanja je dio spremnika i možemo ga dohvatiti pomoću dvije metode:
Operacija | Značenje |
---|---|
|
Vraća objekt koji predstavlja operator uspoređivanja spremnika. |
|
Ima posve isti efekt kao i |
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
istd::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, dokstd::multimap
dozvoljava. -
Spremnici
std::map
istd::multimap
su (najčešće) implementirani kao balansirana binarna stabla. -
Nema metode koje podrazumijevaju poziciju elementa:
push_back
,push_front
,front
,back
,pop_front
ipop_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 |
|
Dodijeljeni konstruktor; kreira prazan |
|
Kreira prazan skup s operatorom uspoređivanja |
|
Konstruktor kopije. Kreira |
|
Konstruktor kopije |
|
Konstruktor kopije premještanjem |
|
Konstruktor kopije premještanjem |
|
Kreira |
|
Kreira |
|
Kreira |
|
Kreira |
|
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.
|
Vraća broj pojavljivanja ključa |
|
Vraća iterator na element indeksiran sa |
|
Vraća iterator koji referira na prvi element čiji ključ nije manji od |
|
Vraća iterator koji referira na prvi element čiji je ključ veći od |
|
Vraća par iteratora od kojih je prvi ekvivalentan s |
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čakey
u spremniku, ako element s tim ključem postoji. Operacijaupper_bound(key)
vraća iterator na prvi element iza posljednjeg elementa s ključemkey
. U toj situaciji par [lower_bound(key)
,upper_bound(key)
) predstavlja raspon elementa s ključemkey
. -
Kada element s ključem
key
ne postoji u spremniku tadalower_bound(key)
iupper_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čemkey
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>
.
Operacija | Značenje |
---|---|
|
Vraća iterator na poziciju prvog elementa |
|
Vraća iterator na poziciju iza zadnjeg elementa |
|
Vraća konstantan iterator na poziciju prvog elementa |
|
Vraća konstantan iterator na poziciju iza zadnjeg elementa |
|
Vraća reverzni iterator na poziciju prvog elementa reverznog niza |
|
Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza |
|
Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza |
|
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.
|
Ubacuje vrijednost |
|
|
|
Ubacuje kopiju inicijalizacijske liste |
-
Kod ubacivanja elementa
e
umap
,insert
ubacuje element u spremnika ako pripadni ključ (e.first
) nije um
. U suprotnomm
ostaje neizmjenjen. Povratna vrijednost je par (std::pair
) čiji je prvi element iterator koji referira na element s ključeme.first
, a drugi jebool
varijabla koja indicira je li element ubačen ili nije. -
Kod ubacivanja elementa
e
umultimap
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 |
|
Ubacuje vrijednost |
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 |
|
Ubacuje u spremnik element konstruiran pomoću |
|
Ubacuje u spremnik element konstruiran pomoću |
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.
|
Izbriši element s ključem |
|
Briše element na koji referira iterator |
|
Briše elemente u rasponu koji određuju iteratori |
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.
|
Vraća referencu na vrijednost koja je pridružena ključu |
|
Vraća referencu na vrijednost koja je pridružena ključu |
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:
|
Skup |
|
Asocijativno polja |
|
Skup s ponavljanjem ključeva |
|
Asocijativno polja s ponavljanjem ključeva |
Za korištenje ovih spremnika treba uključiti zaglavlja <unordered_set>
i/ili <unordered_map>
.
-
unordered_set
iunordered_multiset
čuvaju jednu vrijednost (ključ) dokunordered_map
iunordered_multimap
čuvaju parove ključ/vrijednost. -
unordered_set
iunordered_map
ne dozvoljavaju višestruke elemente s istim ključem, dokunordered_multimap
iunordered_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 |
|
Konstruktor bez argumenata konstruira prazan spremnik bez elemenata |
|
Kreira prazan spremnik koji koristi minimalno |
|
Kreira prazan spremnik koji koristi minimalno |
|
Kreira prazan spremnik koji koristi minimalno |
|
Konstruktor kopije |
|
Konstruktor kopije |
|
Konstruktor kopije premještanjem |
|
Konstruktor kopije premještanjem |
|
Kreira spremnik inicijaliziran elementima iz raspona |
|
Kreira spremnik inicijaliziran elementima iz raspona |
|
Kreira spremnik inicijaliziran elementima iz raspona |
|
Kreira spremnik inicijaliziran elementima iz raspona |
|
Kreira spremnik inicijaliziran elementima iz inicijalizacijske liste |
|
Kreira spremnik inicijaliziran elementima iz inicijalizacijske liste |
|
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:
|
Vraća broj pojavljivanja ključa |
|
Vraća iterator na element indeksiran sa |
|
Vraća par iteratora koji predstavljaju raspon elemenata s ključem |
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>
.
Operacija | Značenje |
---|---|
|
Vraća iterator na poziciju prvog elementa |
|
Vraća iterator na poziciju iza zadnjeg elementa |
|
Vraća konstantan iterator na poziciju prvog elementa |
|
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.
|
Ubacuje vrijednost |
|
|
|
Ubacuje kopiju inicijalizacijske liste |
|
Ubacuje vrijednost |
-
Kod ubacivanja elementa
e
uunordered_set
iunordered_map
, metodainsert
ubacuje element u spremnika ako pripadni ključ nije prisutan uc
. U suprotnomc
ostaje neizmjenjen. Povratna vrijednost je par (std::pair
) čiji je prvi element iterator koji referira na element s ključeme.first
, a drugi jebool
varijabla koja indicira je li element ubačen ili nije. -
Kod ubacivanja elementa
e
umultiset
imultimap
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 |
|
Ubacuje u spremnik element konstruiran pomoću |
|
Ubacuje u spremnik element konstruiran pomoću |
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.
|
Izbriši sve elemente s ključem |
|
Briše element na koji referira iterator |
|
Briše elemente u rasponu koji određuju iteratori |
|
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
.
|
Vraća referencu na vrijednost koja je pridružena ključu |
|
Vraća referencu na vrijednost koja je pridružena ključu |
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 |
|
Vraća hash funkciju |
|
Vraća predikat ekvivalencije |
|
Vraća trenutni broj pretinaca |
|
Vraća maksimalni mogući broj pretinaca |
|
Vraća trenutno opterećenje |
|
Vraća trenutno maksimalni opterećenje |
|
Postavlja maksimalno opterećenje na |
|
Vrši rehaširanje spremnika tako da broj pretinaca bude barem |
|
Vrši rehaširanje spremnika tako da ima prostora za barem |
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)
pozivc.rehash(bnum)
priprema spremnik za barembnum*c.max_load_factor()
elemenata.
Za ispitivanje strukture spremnika imamo i sljedeće metode:
Operacija |
Značenje |
|
Vraća indeks pretinca u koji bi vrijednost |
|
Vraća broj elemenata u pretincu s indeksom |
|
Vraća jednosmjerni iterator koji pokazuje na prvi element u pretincu s indeksom |
|
Vraća jednosmjerni iterator koji pokazuje iza zadnjeg elementa u pretincu s indeksom |
|
Vraća konstantan jednosmjerni iterator koji pokazuje na prvi element u pretincu s indeksom |
|
Vraća konstantan jednosmjerni iterator koji pokazuje iza zadnjeg elementa u pretincu s indeksom |
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}