STL:n algoritmit¶
STL-kirjasto algorithm tarjoaa valmiina yleisimmät operaatiot,
joita säiliön sisältämille alkioille on tarpeen tehdä.
Algoritmikirjaston käyttämiseksi on koodiin lisättävä
#include <algorithm>
STL:n valmiit algoritmit ovat geneerisiä: Ne eivät ota kantaa säiliön
tyyppiin, vaan ne osaavat operoida millä tahansa säiliöllä.
(Tosin aika moni alla lueteltavista algoritmeista ei toimi
map- ja set-rakenteilla, joista kerrotaan seuraavassa
teorialuvussa.)
Käsiteltäväksi haluttu osa säiliön alkioista kerrotaan algoritmifunktiolle iteraattorivälin avulla. Jokaisella funktiolla on aina vähintään kaksi iteraattoriparametria.
Esimerkiksi algoritmi (siis funktio) sort osaa lajitella
vector-rakenteen alkiot kasvavaan järjestykseen, kunhan sille
kerrotaan iteraattoreilla lajiteltavaksi haluttu osaväli:
vector<double> reaalivektori;
...
sort(reaalivektori.begin(), reaalivektori.end());
Kannattaa pitää mielessä, että begin- ja end-iteraattorien
paikalla mikä tahansa kahden iteraattorin esittämä osaväli on kelvollinen:
vector<double>::iterator valin_eka = reaalivektori.begin();
vector<double>::iterator valin_vika = reaalivektori.end();
++valin_eka; // Osoittaa nyt toiseen alkioon
--valin_vika; // Osoittaa nyt viimeiseen alkioon
// Lajitellaan vektorin sisällä kaikki alkiot
// ensimmäistä ja viimeistä lukuunottamatta.
// Ensimmäinen ja viimeinen pysyvät paikallaan.
sort(valin_eka, valin_vika);
Tärkeä huomio: algoritmikirjaston operaatiot eivät koske
jälkimmäisen iteraattoriparametrin osoittamaan alkioon.
Edellisessä esimerkissä siis viimeinen vektorin alkio ei enää
tullut lajitelluksi, koska valin_vika osoitti siihen.
Seuraavassa lyhyt lista muista hyödyllisistä algoritmikirjaston funktioista.
Suurimmassa osassa esimerkkirakenteena on vector, koska se on
tässä vaiheessa ainoa tuttu STL-säliötyyppi.
Kuitenkin, mikäli ei eriksen mainita, funktiot
toimivat muillakin säiliötyypeillä ja merkkijonoilla (string).
countlaskee säiliössä olevien tietyn arvoisten alkioiden lukumäärän:vector<string> vihamiehet; ... // Kuinka monta Akia vihamiehet-vektorista löytyy? cout << count(vihamiehet.begin(), vihamiehet.end(), "Aki") << " kappaletta Aki-nimisia vihamiehia!" << endl;
count-funktio ei toimimap-rakenteella aivan yhtä suoraviivaisesti.min_elementjamax_elementetsivät säiliöstä arvoltaan pienimmän tai suurimman alkion ja palauttavat iteraattorin, joka osoittaa kyseiseen alkioon:vector<int> maarat; ... vector<int>::iterator pienin_it; pienin_it = min_element(maarat.begin(), maarat.end()); cout << "Pienin lukumaara: " << *pienin_it << endl;
min_element- jamax_element-funktiot eivät toimimap-rakenteella aivan yhtä suoraviivaisesti.findetsii säiliöstä haluttua arvoa ja palauttaa iteraattorin ensimmäiseen löytämäänsä alkioon tai käsiteltävänä olevan säiliönend-iteraattorin, jos arvoa ei löydy:vector<string> potilaat; ... vector<string>::iterator iter; iter = find(potilaat.begin(), potilaat.end(), "Kai"); if ( iter == potilaat.end() ) { // Ei ole Kaita potilasjonossa. ... } else { // Kai löytyi ja se sijaitsee iter:in osoittamassa // kohdassa: tulostetaan ja poistetaan jonosta. cout << *iter << endl; // vector-säiliöllä on metodi erase, jolla // iteraattorin osoittama alkio voidaan poistaa. potilaat.erase(iter); }
Kannattaa pitää mielessä, että
string-tietotyypillä ja myöhemmin tutuiksi tulevillaset- jamap-rakenteilla on metodifunktio nimeltäfind, jota kannattaa ehdottomasti käyttää, koska varsinkin kahden jälkimmäisen tapauksessa se on monta kertaluokkaa nopeampi kuin algoritmi-kirjastonfind.replacekorvaa kaikki halutut arvot uudella arvolla:replace(tekstivektori.begin(), tekstivektori.end(), "TTY", "TUNI");
jossa kaikki vektorissa olevat alkiot, joiden arvo on “
TTY” korvataan arvolla “TUNI”.replaceei toimiset-rakenteella taimap-rakenteella.fillmuuttaa kaikki iteraattorivälin alkiot annetun arvoisiksi:string merkkijono = ""; ... // Seuraavan fill-operaation jälkeen koko // merkkijono on täynnä kysymysmerkkejä. fill(merkkijono.begin(), merkkijono.end(), '?');
fillei toimiset-rakenteella taimap-rakenteella.reversekääntää annetun välin takaperoiseen järjestykseen:vector<Pelaaja> vuorojarjestys; ... // Seuraavalla pelikierroksella // vuorojärjestys on käänteinen. reverse(vuorojarjestys.begin(), vuorojarjestys.end());
reverseei toimiset- jamap-rakenteilla ollenkaan.shufflesekoittaa alkiot satunnaiseen järjestykseen:vector<Pelikortti> korttipakka; minstd_rand gen; // Luodaan satunnaislukugeneraattori nimeltä gen ... shuffle(korttipakka.begin(), korttipakka.end(), gen);
shuffleei toimiset- jamap-rakenteilla ollenkaan.C++:ssa on valmiina määritelty useita erilaisia satunnaislukugeneraattoreita, jotka löytyvät kirjastosta
random. Tietotyypinminstd_randavulla voit luoda yhdenlaisen satunnaislukugeneraattorin ja aiemmin esiintyneendefault_random_engine-tyypin avulla toisen. Tässä vaiheessa riittää, että osaat luoda näistä yhden (tai molemmat). Halutessasi voit hakea internetistä lisätietoa C++:n erilaisista satunnaislukugeneraattoreista.copykopioi säiliön alkiot johonkin toiseen säiliöön:string merkkijono = ""; ... // Alustetaan merkkivektori siten, että siinä on // yhtä monta alkiota kuin merkkijonossa on merkkejä. // Huomaa että tässä on taas tilanne, // jossa alustuksessa pitää käyttää kaarisulkeita. vector<char> merkkivektori(merkkijono.length()); // Kopioidaan kaikki merkkijonon merkit // alkioiksi merkkivektoriin. copy(merkkijono.begin(), merkkijono.end(), merkkivektori.begin());
Kopioinnin kohteessa (siis siellä minne alkiota kopioidaan) pitää olla valmiiksi tilaa tarjolla. Edellä
merkkivektorion valmiiksi alustettu sisältämään yhtä monta alkiota kuinmerkkijono:ssa on merkkejä.Tämä pätee kaikkiin STL-algoritmeihin, jotka tallettavat tuloksia säiliöön: Kohdesäiliössä pitää olla valmista tilaa kaikille talletettaville alkioille.
Lähtösäilion ja kohdesäiliön alkioiden tyyppien pitää olla samat.
Useista algorithm-kirjaston funktioista on olemassa versio, jonka
toimintaa voidaan säätää funktioparametrin avulla.
Otetaan tässä esimerkkinä sort-funktio.
Ennen varsinaista asiaa, on tärkeä ymmärtää, että sort-funktio ei
voi lajitella suuruusjärjestykseen kuin sellaista tietoa, jolle on
määritelty jonkinlainen suuruusrelaatio. Tämä ei päde esimerkiksi
vektorille, jonka alkiot ovat värejä, sillä ei ole ollenkaan itsestäänselvää,
onko purppura pienempi vai suurempi kuin vaikkapa violetti, koska
väreillä ei ole suuruusjärjestystä.
Tämä ei ole ongelma silloin, kun lajiteltavassa säiliössä on alkioina kielen perustyyppistä tietoa, merkkijonoja tai joitakin muita C++:ssa valmiiksi määriteltyjä tyyppejä. Mutta heti, jos yritetään käsitellä omatekemiä tyyppejä (luokkia), joita ei pystytä automaattisesti vertailemaan, törmätään ongelmiin.
Tutkitaan pieni esimerkki (tilan säästämiseksi epäolennaisia koodirivejä jätetty pois):
class Opiskelija {
public:
Opiskelija(string const& nimi, int opnum);
string hae_nimi() const;
int hae_opnum() const;
void tulosta() const;
private:
string nimi_;
int opnum_;
};
bool vertaile_opiskelijanumeroita(Opiskelija const& op1,
Opiskelija const& op2) {
return op1.hae_opnum() < op2.hae_opnum();
}
bool vertaile_nimia(Opiskelija const& op1,
Opiskelija const& op2) {
return op1.hae_nimi() < op2.hae_nimi();
}
int main() {
vector<Opiskelija> opiskelijat = {
{ "Teekkari, Tiina", 121121 },
{ "Arkkari, Antti", 111222 },
{ "Fyysikko, Ville", 212121 },
{ "Teekkari, Teemu", 100011 },
{ "Kone, Kimmo", 233233 },
};
// Järjestetään nousevaan järjestykseen opiskelijanumeroiden perusteella
// ja tulostetaan
sort(opiskelijat.begin(), opiskelijat.end(), vertaile_opiskelijanumeroita);
for ( auto opisk : opiskelijat ) {
opisk.tulosta();
}
cout << string(30, '-') << endl;
// Järjestetään nousevaan järjestykseen nimen perusteella
// ja tulostetaan
sort(opiskelijat.begin(), opiskelijat.end(), vertaile_nimia);
for ( auto opisk : opiskelijat ) {
opisk.tulosta();
}
}
Esimerkin funktiot vertaile_opiskelijanumeroita ja
vertaile_nimia ovat nk. vertailufunktioita:
- Parametrit (2 kpl) ovat vakioviitteitä sen tietotyypin alkioihin, joita funktion on tarkoitus vertailla.
- Paluuarvon tyyppi on
bool. - Paluuarvo on
truetäsmälleen silloin, kun ensimmäinen parametri on aidosti pienempi kuin toinen parametri.
Vertailufunktion voi antaa lisäparametrina sellaisille algoritmikirjaston funktioille, joiden toiminta riippuu käsiteltävänä olevan säiliön alkioiden suuruudesta.
Näin ollen edellisen esimerkin vektorista voitaisiin hakea ja tulostaa opiskelijanumeroltaan suurin opiskelija:
vector<Opiskelija>::iterator iter;
iter = max_element(opiskelijat.begin(),
opiskelijat.end(),
vertaile_opiskelijanumeroita);
// Koska seuraava tulosta-metodi kohdistetaan
// iteraattorin osoittamaan olioon, käytetään
// -> -operaattoria pisteen sijaan.
iter->tulosta();
Kun vertailufunktio annetaan parametrina algoritmikirjaston funktiolle, kutsukohdassa käytetään pelkkää funktion nimeä. Jos funktion perässä olisi kaarisulkeet, yrittäisi kutsua funktiota ja antaa parametrina vertailufunktion paluuarvon, mikä ei ole tarkoitus.
Funktioita, jotka saavat parametrina toisen funktion, kutsutaan korkeamman kertaluvun funktioiksi (higher-order function).