STL - unique

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.10: unique e unique_copy

L'algoritmo unique() elimina tutti gli elementi identici consecutivi, tranne uno e ritorna il nuovo iteratore alla fine della sequenza.

É disponibile anche nelle variazioni _copy che permettono di creare una copia della sequenza altrove.

Come remove(), anche unique() non elimina gli elementi superflui in coda alla sequenza. Se necessario, occorre rimuoverli esplicitamente, come faccio alla fine dell'esempio che segue:

#include<iostream>
#include<algorithm>
#include<vector>
#include<iterator>

using namespace std;

int main() {
vector<int> v(20);
for (int i = 0; i < v.size(); ++i)
v[i] = i / 3;

ostream_iterator<int> output(cout, " ");
copy(v.begin(), v.end(), output);
cout << endl;

vector<int>::iterator last = unique(v.begin(), v.end());
copy(v.begin(), last, output);
cout << endl;

copy(v.begin(), v.end(), output);
cout << endl;

v.erase(last, v.end());
copy(v.begin(), v.end(), output);
cout << endl;
}

STL - remove e varianti

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.9: remove, remove_if, remove_copy, remove_copy_ir

L'algoritmo remove(), con la sua variazioni, rimuove tutti gli elementi da una sequenza che sono uguali a un dato valore, o che soddifano il predicato passato, e ritorna la fine della sequenza. Le variazione _copy permettono di specificare una diversa destinazione e ritornano un iteratore in scrittura alla successiva posizione.

É da notare che remove() non fa "tutto" il lavoro. Gli elementi sono eliminati, ma il contenitore non viene "ripulito" degli elementi in coda, che risultano perciò duplicati.

Occorre quindi utilizzare l'iteratore tornato da remove() come nuovo "end" della sequenza, e, se necessario, rimuovere gli elementi duplicati in coda alla sequenza:

#include<iostream>
#include<algorithm>
#include<vector>
#include<iterator>
#include<string>
#include<cstring>
#include<algo.h>

using namespace std;

bool isVowel(char c) {
return strchr("aeiouAEIOU", c) != 0;
}

int main() {
vector<char> v(26);
iota(v.begin(), v.end(), 'a');

ostream_iterator<char> output(cout, "");
copy(v.begin(), v.end(), output);
cout << endl;

cout << "remove 't': " << endl;
vector<char>::iterator last = remove(v.begin(), v.end(), 't');
copy(v.begin(), last, output);
cout << endl;

last = remove_if(v.begin(), last, isVowel);
cout << "only consonants left: " << endl;
copy(v.begin(), last, output);
cout << endl;

cout << "complete sequence up to end() (tail is messed up): " << endl;
copy(v.begin(), v.end(), output);
cout << endl;

v.erase(last, v.end());
cout << "complete 'clean' sequence: " << endl;
copy(v.begin(), v.end(), output);
cout << endl;
}

Creo un vettore di caratteri, inizializzato con tutte le lettere dell'alfabeto inglese, dalla a alla z, usando la funzione iota(); chiamo remove() per eliminare tutte le occorrenze di 't' nell'array (evidentemente ce ne era una sola); da qui in posi uso l'iteratore ritornato, last, invece di end(); chiamo remove_if() passando la funzione isVowel() per rimuovere tutte le vocali; rimuovo la coda del vettore, da last a end(), per ottenere un vettore "pulito".

STL - generate

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.8: generate e generate_n

L'algoritmo generate(), con la sua variazione generate_n(), permette di inizializzare, interamente o parzialmente, una collezione usando una funzione o un functor.

Nell'esempio che segue usiamo il functor Random per generare numeri casuale e la funzione powerOfTwo() per generare iterativamente le potenze di due:

#include<iostream>
#include<cstdlib> // rand() and RAND_MAX
#include<algorithm>
#include<vector>

using namespace std;

void dump(const vector<int>& v) {
vector<int>::const_iterator it = v.begin();
while (it != v.end()) {
cout << *it++ << ' ';
}
cout << endl;
}

class Random {
public:

Random(int b) : range(b) {
srand ( time(NULL) );
}

int operator()() {
return (int) ((double) rand() * range / (RAND_MAX + 1.0));
}
private:
int range;
};

int powerOfTwo() {
static int value = 1;
return (value *= 2) / 2;
}

int main() {
vector<int> v(12);

generate(v.begin(), v.end(), Random(1000));
dump(v);

vector<int>::const_iterator it = generate_n(v.begin(), 10, powerOfTwo);
dump(v); // 1 2 4 8 16 32 64 128 256 512 x y

cout << "it in position = " << (it - v.begin())
<< ", *it = " << *it << endl;

}

STL - fill

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.7: fill e fill_n

L'algoritmo fill(), con la sua variazione fill_n(), permette di inizializzare, interamente o parzialmente, una collezione. L'iteratore ritornato da fill_n() punta al primo elemento successivo a quelli modificati:

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

void dump(const vector<double>& v) {
vector<double>::const_iterator it = v.begin();
while (it != v.end()) {
cout << *it++ << ' ';
}
cout << endl;
}

int main() {
vector<double> v(8);
// initialize all values with 9.23
fill(v.begin(), v.end(), 9.23);
dump(v);

vector<double>::const_iterator it = fill_n(v.begin(), 3, 1.01);
dump(v);

cout << "it is in position = " << (it - v.begin())
<< ", *it = " << *it << endl;
}

STL - replace e variazioni

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.6: replace, replace_if, replace_copy, replace_copy_if

L'algoritmo base di replace() sostituisce ogni occorrenza del valore specificato nell'intervallo passato con un nuovo valore indicato.

La variante replace_if() permette di delegare a un predicato la decisione se effettuare o meno la sostituzione.

Con replace_copy e replace_copy_if l'algoritmo viene generalizzato permettendo di specificare dove vogliamo che vengano copiati i valori dell'intervallo passato.

Nell'esempio che segue vediamo all'opera replace() e le sue varianti:

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>

using namespace std;

void dump(vector<string>& v) {
vector<string>::const_iterator it = v.begin();
while (it != v.end()) {
cout << *it++ << ' ';
}
cout << endl;
}

// unary predicate as functor

class Citrus {
public:

bool operator()(const std::string& a) {
return a == "lemon" || a == "orange" || a == "lime";
}
};

int main() {
vector<string> basket(3), crate(3);
basket[0] = "apple";
basket[1] = "orange";
basket[2] = "lemon";
dump(basket); // apple orange lemon

cout << "replace apple with quince:" << endl;
replace(basket.begin(), basket.end(), string("apple"), string("quince"));
dump(basket); // quince orange lemon

cout << "replace_if citrus fruits with plums:" << endl;
replace_if(basket.begin(), basket.end(), Citrus(), string("plum"));
dump(basket); // quince plum plum

cout << "replace_copy: plums with limes:" << endl;
replace_copy(basket.begin(), basket.end(), crate.begin(),
string("plum"), string("lime"));
dump(crate); // quince lime lime

cout << "replace_copy_if: citrus fruits with tomatoes:" << endl;
replace_copy_if(crate.begin(), crate.end(), basket.begin(), Citrus(),
string("tomato"));
dump(basket); // quince tomato tomato
}

Il functor Citrus fa da predicato; replace() sostituisce nel basket apple con quince; replace_if() usa il predicato per sostituire gli agrumi nel basket con plum; replace_copy() copia gli elementi dal basket al crate, sostituendo plum con lime; replace_copy_if() copia gli elementi del crate nel basket, sostituendo gli agrumi con tomato.

STL - transform

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.5: transform

Se oltre a copiare elementi di un container vogliamo anche applicare dei cambiamenti, l'algoritmo da utilizzare é transform() che é disponibile in due variazioni, a seconda del fatto se vogliamo lavorare su uno o due elementi alla volta.

I parametri passati sono gli iteratori che delimitano la sequenza su cui lavorare e l'operazione, unaria o binaria, che vogliamo applicare sugli elementi stessi.

Nell'esempio che segue definiamo l'operazione unaria come funzione e la binaria come functor - poteva essere anche il contrario. Facciamo prima una modifica unaria "in place" e poi una binaria il cui output é mandato in un altro container.

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>

using namespace std;

// unary operation as function
string toUppercase(string s) {
for (size_t i = 0; i < s.length(); ++i)
if (s[i] >= 'a' && s[i] <= 'z')
s[i] -= 'a'-'A';
return s;
}

// binary operation as functor
class Join {
public:

string operator()(const string& a, const std::string& b) {
return a + " and " + b;
}
};

int main() {
vector<string> gals(3), guys(3), couples(3);

gals[0] = "Annabella";
gals[1] = "Scheherazade";
gals[2] = "Xaviera";
guys[0] = "Bogey";
guys[1] = "Amadeus";
guys[2] = "Wladimir";

// transformation in place
transform(guys.begin(), guys.end(), guys.begin(), toUppercase);

// copy and transform
transform(gals.begin(), gals.end(), guys.begin(), couples.begin(), Join());

vector<string>::const_iterator it = couples.begin();
while(it != couples.end()) {
cout << *it++ << endl;
}
}

La prima chiamata a transform() modifica tutti gli elementi di guys usando la funzione toUppercase(), e mettendo il risultato ancora in guys, sempre a partire dal primo elemento - dunque la variazione viene fatta in place.

La seconda volta prendiamo in input tutti gli elementi di gals e guys, li modifichiamo usando il functor Join e mettiamo i risultati in couples.

STL - swap

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.4: swap, iter_swap e swap_ranges

Si tratta di algoritmi per scambiare elementi di container.

Lo swap scambia due elementi che possono essere nello stesso o in differenti container.

Lo iter_swap prende due iteratori che fanno riferimento allo stesso o a differenti container e scambia gli elementi associati.

Lo swap_ranges permette di scambiare due intervalli che possono essere nello in differenti o nello stesso container - in questo caso bisogna fare attenzione al fatto che gli intervalli non si sovrappongano.

Per vedere il risultato dello swap sul vettore uso questa funzioncina d'appoggio:

void dump(vector& v) {
vector::const_iterator it = v.begin();
while (it != v.end()) {
cout << *it++ << ' ';
}
cout << endl;
}


Questo il resto del codice d'esempio:

#include<algorithm>
#include<algo.h>
#include<vector>

using namespace std;

int main() {
vector<int> v(15);
iota(v.begin(), v.end(), 10);
dump(v);

cout << "Swap elements v[3] and v[5]:\n";
swap(v[3], v[5]); // swap
dump(v);

cout << "swap first and last elements via iterator:" << endl;
vector<int>::iterator first = v.begin();
vector<int>::iterator last = v.end();
--last;
iter_swap(first, last); // swap
dump(v);

int third = v.size() / 3;
cout << "swap head and tail " << "(" << third << " positions):" << endl;
vector<int>::iterator middle1 = v.begin();
advance(middle1, third); // end of first third
vector<int>::iterator middle2 = v.end();
advance(middle2, -third); // beginning of second third
swap_ranges(first, middle1, middle2); // swap

dump(v);
}

Inizializzo il vettore usando la solita iota. Faccio un primo swap, passando due elementi dell'array per reference. Poi un secondo swap, passando l'iteratore che punta al primo e all'ultimo (valido) elemento dell'array.

Infine faccio uno swap di intervallo, scambiando il primo terzo del vettore con l'ultimo.

STL - copy e copy_backward

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.2: copy e copy_backward

Questo algoritmo copia elementi da un intervallo ad una destinazione. La destinazione può essere l'inizio o la termine (nel caso di copy_backward) della copia. É un algoritmo che va usato con cautela, infatti c'é il rischio di sovrapposizione tra input e output, cosa che può causare risultati indefiniti.

Nell'esempio inizializziamo un vettore usando iota, poi lo copiamo in un altro vettore.

Più interessante il secondo utilizzo di copy, che copia il vettore in un ostream_iterator diretto verso cout e con l'uso di un asterisco come separatore. In pratica la copia risulta essere un dump del vettore sulla console.

Segue infine una backward_copy con sovrapposizione (benigna) dell'intervallo di input con quello di output.

#include<algorithm>
#include<vector>
#include<iterator>
#include<algo.h>

using namespace std;

int main() {
vector<int> v1(7), v2(7, 0);
iota(v1.begin(), v1.end(), 0);

vector<int>::const_iterator it = v2.begin();
while(it != v2.end()) {
cout << *it++;
}
cout << endl;

copy(v1.begin(), v1.end(), v2.begin());
it = v2.begin();
while(it != v2.end()) {
cout << *it++ << ' ';
}
cout << endl;

// copy v1 to cout, separator *
ostream_iterator<int> output(cout, "*");
copy(v1.begin(), v1.end(), output); // 0*1*2*3*4*5*6*
cout << endl;

// overlapping ranges:
vector<int>::iterator last = v1.begin();
advance(last, 4); // 4 steps forward
copy_backward(v1.begin(), last, v1.end());

copy(v1.begin(), v1.end(), output); // 0*1*2*0*1*2*3*
cout << endl;
}

STL - iota

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che modificano le sequenze su cui operano.

5.4.1: iota

Non é un algoritmo incluso nello standard C++, ma spesso viene reso disponibile. Nel mio caso cygwin lo mette a disposizione includendo l'header algo.h; il suo scopo é quello di inizializzare la sequenza passatagli con valori crescenti, a partire dal valore passato come terzo parametro.

Nell'esempio qui sotto un vettore viene inizializzato a partire dal valore 3 (seguono 4, 5, ...):

#include<algo.h>
#include<vector>
#include<iostream>

using namespace std;

int main() {

vector<int> v(5);
for(size_t i = 0; i < v.size(); ++i)
cout << v[i] << ' ';
cout << endl;

iota(v.begin(), v.end(), 3);

for(size_t i = 0; i < v.size(); ++i)
cout << v[i] << ' ';
cout << endl;
}

STL - search

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che non modificano le sequenze su cui operano.

5.3.9: search

L'algoritmo search cerca una sequenza di dimensione N per vedere se contiene una seconda sequenza di dimensione G.

Ritorna un iteratore al punto in cui inizia la seconda sequenza all'interno della prima, o end().

Nell'esempio che segue creiamo due vettori di interi tali che il secondo sia una sottosequenza del primo.

La search, evidentemente, avrà successo e ritornerà l'iteratore che punta al primo elemento della sottosequenza all'interno del primo vettore.

Cambiamo ora segno alla sottosequenza e ripetiamo la ricerca utilizzando come predicato usando il functor AbsIntCompare che ritorna true in caso di uguaglianza dei valori passati in termini assoluti. Otterremo lo stesso risultato del passaggio precedente.

Naturalmente quando chiamiamo search senza passare il predicato per la sottosequenza negativizzata la search fallisce, ovvero ritorna l'iteratore end() del primo vettore.

#include<algorithm>
#include<vector>
#include<iostream>
#include<cstdlib>

using namespace std;

class AbsIntCompare { // ignore signs
public:

bool operator()(int x, int y) {
return abs(x) == abs(y);
}
};

int main() {
vector<int> v1(12);
for (size_t i = 0; i < v1.size(); ++i)
v1[i] = i; // 0 1 2 3 4 5 6 7 8 9 10 11 12
vector<int> v2(4);
for (size_t i = 0; i < v2.size(); ++i)
v2[i] = i + 5; // 5 6 7 8

vector<int>::const_iterator where;

cout << "Search for substructure v2 in v1: ";
where = search(v1.begin(), v1.end(), v2.begin(), v2.end());
if (where != v1.end()) {
cout << "v2 starts at position " << (where - v1.begin()) << endl;
}

// change signs
for (size_t i = 0; i < v2.size(); ++i)
v2[i] *= -1; // –5 –6 –7 –8

cout << "Search for substructure v2 in v1, ignoring signs: ";
where = search(v1.begin(), v1.end(), v2.begin(), v2.end(), AbsIntCompare());
if (where != v1.end()) {
cout << "v2 starts at position " << (where - v1.begin()) << endl;
}

// can't find the negative sequence with the standard search
cout << "Search for substructure v2 in v1: ";
where = search(v1.begin(), v1.end(), v2.begin(), v2.end());
if (where == v1.end()) {
cout << "can't find v2." << endl;
}
}

STL - equal

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che non modificano le sequenze su cui operano.

5.3.8: equal

confronta due container, con una variante che usa un predicato binario. É quindi molto simile a mismatch con la differenza fodnamentale che ritorna un booleano, sappiamo solo se le due sequenze sono uguali o no, ma non dov'é la prima differenza.

A seguire l'esempio visto per mismatch, riscritto per equal:

#include<iostream>
#include<algorithm>
#include<vector>
#include<set>

using namespace std;

int main() {
vector<int> v(8);
for (size_t i = 0; i < v.size(); ++i)
v[i] = 2 * i; // sorted sequence
set<int> s(v.begin(), v.end()); // initialize set with v

if (equal(v.begin(), v.end(), s.begin()))
cout << "No mismatch found." << endl;

++v[3]; // insert mismatch

// comparison for match with iterator pair ’where’
if (equal(v.begin(), v.end(), s.begin()) == false) {
cout << "Mismatching sequences detected." << endl;
}
}

STL - mismatch

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che non modificano le sequenze su cui operano.

5.3.7: mismatch

confronta due container, con una variante che usa un predicato binario.

Come esempio creo un vettore di otto interi, inizializzati con i primi numeri pari. Copio gli elementi in un set di interi, usando il costruttore di set che accetta in input gli iteratori che delimitano i valori che voglio copiare.

Definisco un pair il cui primo elemento é un iteratore a un vettore di interi e il secondo un iteratore a un set di interi.

La funzione mismatch() ritornerà, per l'appunto, un pair di questo tipo, dato che gli passiamo gli iteratori di quel tipo.

Dato che le due sequenze sono identiche per costruzione, ci aspettiamo che il risultato della chiamata a mismatch() sia che non si sono trovate differenze, ovvero, il primo elemento del pair sarà uguale all'iteratore end() del vettore.

Modifichiamo un elemento del vettore e ripetiamo la chiamata a mismatch(). Ora ci aspettiamo di trovare una differenza, e questa sarà segnalata dal fatto che il primo elemento del pair sarà un iteratore diverso dall'end() del vettore, e in particolare punterà alla diffenza.

Ecco il codice:

#include<iostream>
#include<algorithm>
#include<vector>
#include<set>

using namespace std;

int main() {
vector<int> v(8);
for (size_t i = 0; i < v.size(); ++i)
v[i] = 2 * i; // sorted sequence
set<int> s(v.begin(), v.end()); // initialize set with v

pair<vector<int>::iterator, set<int>::iterator> where;

where = mismatch(v.begin(), v.end(), s.begin());
if (where.first == v.end())
cout << "No mismatch found." << endl;

++v[3]; // insert mismatch

// comparison for match with iterator pair ’where’
where = mismatch(v.begin(), v.end(), s.begin());
if (where.first != v.end()) {
cout << "First mismatch (" << *where.first << " != "
<< *where.second << ") found at position "
<< (where.first - v.begin()) << "." << endl;
}
}