dijous, 6 de juliol del 2017

Aprendre a programar 11 - Introducció a la Programació Modular

Doncs sembla que ja hem acabat PRO1. Arribats a aquest punt, espero que als lectors els hagin agradat els meus articles, i els hagin ajudat a entendre coses. Aquí començarem a explicar l'assignatura de PRO2. En aquesta, el laboratori està més dividit en sessions, així que jo també ho faré així. Posaré més o menys de quina sessió és cada cosa. Ara començarem pel principi, que és la sessió 1 i la sessió 2. Les sessions i tot el que és necessari està a disposició de tothom, així que no cal estar estudiant PRO2 per fer això

Què és la programació modular?

La programació modular consisteix en dividir un programa en subprogrames més petits. Això ens permet tenir diferents mòduls escrits per diferents programadors, així com fer-lo més llegible i modificable. En essència, dividim un problema en problemes més petits, que són més fàcils de resoldre, i els resolem. Finalment, ho ajuntem tot. En C++, això es fa utilitzant una eina, que és la Classe. 

Què és una classe?

Una classe és una abstracció d'un objecte. Conté dins mètodes i atributs. Aquests poden ser privats, protegits i públics, tot i que a PRO2 els protegits no s'utilitzen. Per exemple, podem tenir una classe com la següent:

class Complex {
private:
double r, i;
public:
void inicialitzar (double real, double imaginaria) {
r=real;
i=imaginaria;
}
double real() {
return r;
}
double imaginaria() {
return i;
}
void mostrar() {
if(i<0) cout <<r<<" - "<<abs(i)<<'i'<<endl;
else cout <<r<<" + "<<i<<'i'<<endl;
}
};

Aquí tenim una classe que serviria per representar nombres complexos. Podem veure que d'atributs tenim dos nombres reals, corresponents a la part real i la part imaginària. No obstant, fixem-nos que són privats. Això ens impedeix accedir-hi directament. Per accedir-hi hem d'utilitzar les funcions públiques. És a dir, podem veure dos codis:

//Codi erroni, intentem accedir als atributs privats
Complex c;
c.r=3;
c.i=4;
c.mostrar();

//Codi correcte
Complex c;
c.inicialitzar(3,4);
c.mostrar();

Podem veure que no li passem c com a paràmetre. Això és perquè cada objecte (cada variable que creem amb una classe és un objecte) és propietari dels seus mètodes. 

Utilitzar classes permet que qui les utilitzi no s'hagi de preocupar gens per com funcionen les coses internament. Què més dóna si emmagatzemem la part real i la part imaginària en dues variables, en una parella, o de qualsevol manera? Només ens importa que ens les doni si les demanem. 

Com utilitzem les classes?

Inicialment no en programarem cap, sinó que utilitzarem les que ens donen. Ens donaran bàsicament tres fitxers, que són les capçaleres, el codi i un pdf on explica què tenim. 

Espera, per què fitxers diferents?

Hem dit que una de les raons per utilitzar programació modular és que podem tenir diversos programadors programant diferents mòduls. Aleshores, hem de permetre que cada mòdul es programi en un fitxer diferent, i després es pugui ajuntar fàcilment (i no em refereixo a copiar el codi de tots els mòduls en un únic fitxer). Per tant, el que es fa normalment és crear dos fitxers per cada mòdul

Capçaleres:

Podem dir que és un fitxer que ens diu "què tindrem". És a dir, conté les declaracions de les classes, funcions i tot el que necessitem, però sense el seu codi. És com dir-nos "ei, quan ajuntis el codi amb el meu fitxer, tindràs aquestes funcions". Per això, aquest fitxer l'ha de tenir tothom qui pretengui utilitzar el mòdul. Nosaltres farem que sigui un fitxer amb extensió hh (la h de headers), i l'inclourem mitjançant la directiva
#include "capçalera.hh"
Utilitzem "" enlloc de <> perquè, en el cas de <>, primer busca als directoris estàndard (que és on troba iostream, vector, algorithm i totes aquestes) i si no ho troba allà, se'n va al nostre directori. En canvi, amb "", primer busca al nostre directori i, si no ho troba, se'n va allà. 

Codi:

Aquí tenim el codi de tot el que hem declarat a les capçaleres. És un fitxer amb extensió cc, com els que hem utilitzat sempre. Aquest es pot compilar per separat de la resta, quedant un fitxer amb extensió o (d'objecte), i només necessitem tenir les capçaleres que utilitzarem, sense el seu codi. Per exemple, si tenim una classe Rectangle, que utilitza la classe Punt, només necessitarem Rectangle.hh, Rectangle.cc i Punt.hh. Quan compilem, tindrem un arxiu Rectangle.o. Això ens permet utilitzar classes sense saber ni quin és el seu codi, ja que a l'hora d'enllaçar tots els codis, utilitzarem els fitxers .o

Aleshores, com va això de compilar i enllaçar?

Fàcil. Primer compilem tots els mòduls, i després els enllacem. Així:
$g++ -c Classe1.cc
...
$g++ -c main.cc
$g++ Classe1.o Classe2.o ... main.o -o programa.exe

És a dir, primer compilem utilitzant -c, i un cop tenim tots els mòduls (recordem que ens cal un main que cridi tot), podem linkar. Utilitzant -o, diem com volem que es digui l'arxiu de sortida. Fixem-nos, però, que molts professors de PRO2 prefereixen fer
$g++ -o programa.exe Classe1.o Classe2.o ... main.o
És el mateix, simplement ho canvien d'ordre. Jo prefereixo la primera forma, però crec que és també qüestió de gustos.
Per altra banda, a PRO2 s'utilitza p2++, que és un àlies, igual que ho era p1++. Jo penso que és important utilitzar-lo, i parlo des de l'experiència, ja que no vaig arribar a utiltizar p1++ ni p2++. Si no el fas servir, et pots trobar que executes un programa que utilitza alguna cosa de C++11, i no et funciona (recordem que p2++ utilitza C++11). Si decideixes passar olímpicament, al menys recorda utilitzar els flags -Wall i -std=c++11

Comencem: La classe Estudiant

Els primers exercicis que utilitzen classes no estàndard són els que utilitzen Estudiant. Per utilitzar-ho ens donen un fitxer .pdf on crec recordar que està ben explicat. No obstant, en el moment que escric això, no tinc accés al disc de la FIB, i parlo de memòria. Per sort, m'he aconseguit els .hh i .cc. 
La classe en qüestió conté les següents funcions

Estudiant(): Declara un estudiant sense cap paràmetre. Concretament, li posa dni 0, i cap nota
Estudiant(int dni): Declara un estudiant amb dni=dni, i sense nota. El dni ha de ser >=0

void afegir_nota(double nota): Li afegeix una nota a un estudiant sense nota. Cal que estigui entre 0 i nota_maxima() incloses, cosa que en general, vol dir 0<=nota<=10. No obstant, no sempre és així
void modificar_nota(double nota): Modifica la nota. Cal que tingui nota abans

int consultar_DNI() const: Retorna el dni de l'estudiant
double consultar_nota() const: Retorna la nota de l'estudiant
static double nota_maxima(): Retorna la nota màxima d'un estudiant (per tots serà la mateixa). És static perquè així no cal declarar cap estudiant, pots fer Estudiant::nota_maxima(), ja que per tots serà la mateixa
bool te_nota()  const: Retorna true si l'estudiant té nota, false si no

void llegir(): Llegeix pel canal estàndard un estudiant (és a dir, dos enters, el dni i la nota). Si la nota no està al rang [0,nota_maxima()], l'estudiant resultant no té nota
void escriure() const: Escriu pel canal estàndard de sortida l'estudiant. Si no té nota, escriu un NP

És a dir, per utilitzar la classe Estudiant, tenim aquestes, i només aquestes (records de FM) funcions. Les dues primeres són les constructores, serveixen per crear un estudiant. Les 2 següents, són per modificar un estudiant ja existent. Les 4 següents, per consultar en un estudiant, i les dos últimes, de lectura i escriptura.
Per exemple, podríem tenir un exercici que digui "donat un estudiant i una sèrie de notes, treu pel canal estàndard el seu dni i la nota màxima". Podríem fer-ho així:

int main() {
    int dni;
    cin >>dni;
    Estudiant e(dni);
    double nota;
    while (cin >>nota) {
        if (e.te_nota()) {
            if (nota>e.consultar_nota() and nota<=e.nota_maxima())
                e.modificar_nota(nota);
        }
        else {
            if (nota>=0 and nota<=e.nota_maxima()) e.afegir_nota(nota);
        }
    }
    e.escriure();
}


Suposo que es veu més o menys com funciona. Bàsicament una manera senzilla d'imaginar-se això és
- Un objecte és la representació informàtica d'alguna cosa. El cotxe de la teva mare, o tu
- Una classe és l'abstracció d'un objecte. La classe Cotxe, per exemple, o la classe Estudiant
- Per tant, cada instància que fem d'una classe és un objecte
Aleshores, per interactuar amb qualsevol objecte, ens ho imaginem com una caixa negra. No podem veure què hi ha dins, i, per tant, no ho podem modificar. Però tenim les funcions que ens permeten interactuar amb ell, ja sigui consultar coses, modificar-les...

Resum de la classe Estudiant:

Com es crea un estudiant?
Crear un estudiant es fa amb les funcions creadores. En aquest cas tenim dues versions. La primera crea un estudiant buit, la segona amb dni. Com que no podem modificar el dni de l'estudiant, la primera versió serà només per llegir-lo per l'entrada estàndard, mentre que la segona serà la que ens permetrà afegir-li la nota directament. Es criden igual que quan creàvem una variable entera, o un vector (recordem que el vector és una classe). És a dir
Estudiant e1;//Crea un estudiant buit
Estudiant e2(666);//Crea un estudiant amb el DNI de Satanàs
Com es modifica l'estudiant?
Bàsicament podem actuar sobre la nota. Hi ha dues possibilitats. Si no té nota, li podem afegir, mentre que si ja la té, la podem modificar. Per exemple
Estudiant e(666);
double d, d2;
cin >>d;
e.afegir_nota(d);
cin >>d2;
if(d2>d) e.modificar_nota(d2);
Aquí no he tingut en compte què passaria si una de les notes és <0, o >nota_maxima(), perquè encara no he explicat les consultores. Però caldria fer-ho
Com consultem informació d'un estudiant?
Aquí tenim més coses que podem consultar. Podem consultar el seu DNI. Podem consultar també la seva nota. O la nota màxima, ja que no necessàriament ha de ser 10. Finalment, podem consultar si té nota o no, perquè potser encara no té nota. Es faria així:

Estudiant e;
...
double nota;
cin >>nota;
if (e.te_nota()) {
    if (nota>e.consultar_nota() and nota>=0 and nota<=e.nota_maxima())
        e.modificar_nota(nota);
}
else {
    if (nota>=0 and nota<=e.nota_maxima()) e.afegir_nota(nota);
}
Com es llegeix i escriu?
Aquesta és la més fàcil de totes. Simplement tenim dues funcions, una llegeix i una escriu. La de lectura llegeix dos números, un és el DNI i un és la nota. La d'escriptura escriu el DNI i la nota, si en té, o "NP" si no


Dit tot això, jo crec que ja es poden resoldre tots els exercicis de la classe Estudiant

La classe Cjt_estudiants:

Un cop ja hem entès a la perfecció la classe Estudiant, comencem amb la classe Cjt_estudiants. Aquesta és una classe més complexa, que emmagatzema una sèrie d'estudiants. Té més funcions públiques, que són


Cjt_estudiants(): Constructora. Crea un conjunt buit

void afegir_estudiant(const Estudiant &est): Afegeix l'estudiant est. Aquest no hi ha de ser, i el conjunt ha de tenir espai
void modificar_estudiant(const Estudiant &est): Modifica l'estudiant amb el DNI d'est. 
void modificar_iessim(int i, const Estudiant &est): Modifica l'estudiant a la posició i

int mida() const: Ens dóna la mida del conjunt
static int mida_maxima(): Ens dóna la mida màxima. 
bool existeix_estudiant(int dni) const: Ens diu si existeix un estudiant amb DNI=dni
Estudiant consultar_estudiant(int dni) const: Ens retorna l'estudiant amb DNI=dni. Ha d'existir
Estudiant consultar_iessim(int i) const: Ens retorna l'estudiant a la posició i. 1<=i<=mida()

void llegir(): Llegeix un estudiant pel canal estàndard. Primer llegeix un número n, i després n Estudiants
void escriure(): Escriu els estudiants en ordre ascendent per dni

El que hem de tenir controlat és que els estudiants estan ordenats de manera creixent per dni. Això fa que consultar un estudiant per dni tingui cost logarítmic (fem una cerca dicotòmica), mentre que tenint la posició, té cost constant

Podem provar de resoldre el problema X74882, Actualitzar un conjunt d'estudiants. 

void modificar(Cjt_estudiants& c1, const Cjt_estudiants& c2) {
int n=c1.mida();
for (int i=1;i<=n;++i) {
Estudiant e1, e2;
e1=c1.consultar_iessim(i);
e2=c2.consultar_iessim(i);
if (e1.te_nota()) {
if (e2.te_nota() and e1.consultar_nota()<e2.consultar_nota()) 
c1.modificar_iessim(i,e2);
}
else if (e2.te_nota()) c1.modificar_iessim(i,e2);
}
}

int main() {
Cjt_estudiants c1, c2;
c1.llegir();
c2.llegir();
modificar(c1,c2);
c1.escriure();
}

Bàsicament la funció el que fa és recórrer tot el conjunt. Com que sabem que està ordenat, i que consultar l'ièssim és molt més eficient, anem consultant l'element 1, després el 2, després el 3, i així fins al final. 

diumenge, 2 de juliol del 2017

Aprendre a programar aux - C++11

Els llenguatges de programació evolucionen. Alguns més, i alguns menys. No obstant, els que no evolucionen estan condemnats a fracassar. I C++ és un llenguatge amb molts anys.

Una mica d'història

Als anys 70, Dennis Ritchie i Ken Thompson van voler portar Unix del PDP-7 al PDP-11, segons diu la llegenda, per jugar a un joc. No obstant, Unix estava programat en assemblador, i per tant el codi no era compatible. Aleshores, podrien haver arreglat el codi perquè funcionés en el PDP-11, però se'ls va acudir una cosa millor: Traduir el codi a un llenguatge que es pogués utilitzar en computadors amb assembladors diferents. 

Primer van pensar d'utilitzar el llenguatge B, que havia sigut dissenyat per Thompson i desenvolupat pels dos. No obstant, B no podia aprofitar diverses de les avantatges que tenia el PDP-11, tals com l'adreçament a nivell de byte (en B, es treballava sempre a nivell de word). Això els va portar a desenvolupar un nou llenguatge, conegut com a C (ja que és la lletra següent a la B). Estem a l'any 1972. Poc després van aparèixer les structs, fent que ja el llenguatge fos prou poderós com per programar gran part d'Unix en C.

Perquè us feu una idea, els sistemes operatius estan tots programats en C o C++. Els aparells empotrats (sí, es diuen així), també. Molts servidors també. C és el llenguatge dominant, està fins i tot a la sopa. Perquè us feu una idea, si estudieu a la FIB, només en els dos primers cursos utilitzareu C o C++ a PRO1, PRO2, EC, SO, CI, EDA, AC, IDI... No està malament, no?

C++

L'any 1980, algú pensa que a C li falta orientació a objectes. Així que es decideix afegir-li. Li toca a Bjarne Stroustrup. Inicialment se'l coneix com a C amb classes. No obstant, al 1983, Rick Mascitti, presumint d'originalitat informàtica decideix que, ja que és un increment de C, podem dir-li C++. C++ incorpora una sèrie de coses noves:

bool

Sí. En C, les comparacions retornaven un enter, que era 0 si era false, i diferent de 0 si era true. En C++, es crea el tipus bool, que només pot ser true o false (tot i que podem fer trampes per donar-li altres valors). A partir d'això, les comparacions retornen booleans, enlloc de retornar enters

wchar_t

En C els caràcters eren ASCII, i punt. Hi ha, com ja deveu saber, 128 caràcters ASCII. No obstant, amb tants idiomes com existeixen, necessitem molts més. Així apareix Unicode. No obstant, un caràcter Unicode acostuma a necessitar més d'un byte, mentre que en general, els chars ocupen un byte. Per això apareix wchar_t. En C ja existia, però no era un tipus, sinó que era 
typedef unsigned short wchar_t;
En C++ és un tipus. Té les seves pròpies funcions, classes i operadors, que tenen una w davant. Tenim wstring enlloc de string, wcout enlloc de cout...

main:

En C, el main teòricament havia de retornar un enter. No obstant, això no es complia, i vèiem coses com

int main()//La correcta
void main()
main()//Sí, sense tipus, pa qué
int main(int argc, char* argv[]);//També correcta
...
En C++ això s'acaba. El main té tres opcions, i si no és una d'aquestes, donarà error

int main();
int main (int argc, char* argv[]);
int main (int argc, char* argv[], char* env[]);

La primera és la que hem utilitzat sempre. La segona, rep paràmetres, concretament, un array de strings de C, i un enter amb la llargada d'argv. Aquest tindrà mida 1 com a mínim, perquè el primer paràmetre sempre és el nom de l'executable. 

Classes:

Bé, C++ és C amb classes, per tant no poden faltar les classes. Per saber què és una classe, hem de saber què és un objecte.
Un objecte és la representació informàtica d'alguna cosa. Normalment aquesta cosa existeix d'alguna manera. Per exemple, pot ser el cotxe del veí del 3r 1a
Una classe és l'abstracció d'un objecte. El cotxe del veí del 3r 1a, i el cotxe de la meva mare són cotxes els dos. Podem fer la classe cotxe, que tindrà atributs com les dimensions, el color, el model, el motor, les places... I, si volem representar el nostre cotxe, farem una instància d'aquesta classe amb les dades corresponents

Aleshores, una classe té una sèrie d'atributs, i una sèrie de funcions pròpies. Nosaltres la veiem com una capsa negra amb la que podem interactuar només amb una sèrie de funcions i operadors. No ens interessa saber com desem les dimensions, simplement que ens les doni si les necessitem. 

Un exemple és el vector. No necessitem saber com s'emmagatzema, simplement sabem que té l'operador [] per accedir als elements, que podem consultar-li la mida... Ens és igual que desi la mida (el que fa) o que la calculi (com a curiositat, la classe list, abans de C++11, calculava la mida cada cop que li demanàvem). Ens és igual que els elements s'emmagatzemin seguits (com ho fan), o que no...

Gràcies a les classes podem tenir vectors i strings. Els dos funcionen similar, un punter al primer element, un enter sense signe amb la mida, i una sèrie de funcions per interactuar.

Sobrecàrrega d'operadors

C++ permet sobrecarregar un operador. Això vol dir que, per exemple, podem crear l'operador < per una classe pròpia, i utilitzar-lo. Però no només amb classes, sinó també amb structs i enumeracions. 

C++11

Ara arriba la part important. C++11, també conegut com a C++0x (perquè havia de sortir abans del 2010, però no se sabia ben bé quan) és una gran actualització de C++, que li afegeix moltes funcionalitats noves. Algunes d'elles, molt i molt útils. 

Llistes d'inicialització:

En C, quan teníem un array, podíem fer una cosa així:

int arr[]={1,2,3,4,5};

Això amb els vectors no es podia fer. Al menys fins a C++11. Aquí apareixen llistes d'inicialització, que serveixen per tot tipus d'estructures de dades. Podem fer-ho amb vectors, amb structs, amb classes... Amb qualsevol cosa. Si, per exemple, volem fer un programa que faci els salts d'un cavall dels escacs, podem fer

const vector<pair<int,int>> SALTS={{1,2},{2,1},{-1,2}...};

S'amplia més a l'article sobre la STL

El puto espai dels templates

Suposo que algú s'haurà trobat que si fa
vector<vector<int>> Mat;
Li salta error. En canvi, si posem un espai entre els dos >, això ja no passa. C++11 ho soluciona, i ja no cal. Perquè realment era absurd

Inferència de tipus

Recordeu que, sempre que programem, hem de donar tipus a les variables? Hi ha vegades que és molt pesat. Per exemple, imaginem que volem utilitzar un iterador per recorrer el vector SALTS d'abans, que inicialment apunti al primer element. Hauríem de fer
vector<pair<int,int>>::iterator it=SALTS.begin();
No obstant, si la funció begin() retorna un iterador, no podríem deixar al compilador que decideixi de quin tipus serà it? La resposta és que sí, C++11 permet fer-ho

auto it=SALTS.begin();

No obstant, això només funciona en casos on estigui molt clar el tipus. El compilador tampoc és endeví. Si fem
auto x;
cin >>x
Se'ns queixarà, i amb raó

For en un rang

Quan volem recórrer tot un vector per fer alguna cosa, és un gran pal. Segur que algun cop heu desitjat que hi hagi una manera de recórrer tot un vector més fàcilment. Doncs C++11 porta aquesta manera

vector<int> v;
...
int parells=0;
for(int i:v) {
    if(i%2==0) ++parells;
}

Això agafarà tots els elements de v (li direm i a cada enter), i, per cada un, mirarà si és parell o no. També el podem utilitzar per llegir un vector

vector<int> v(n);
for (int& x:v) cin >>x;

Si posem &, la x serà una referència a l'element. Si no, serà una còpia, de manera que no la podrem modificar. 

vector<vector<int>> m(n,vector<int>(n));
for(auto& x:m) for(auto& y:x) cin >>y;

Utilitzant auto, aquests fors es fan molt més curts encara. En aquest cas, amb un codi molt curt llegim una matriu
Aquest for es pot fer amb qualsevol estructura de dades que tingui iteradors. És a dir, ho podem utilitzar amb vectors, llistes, sets, maps... Fins i tot amb arrays, que també tenen iteradors (amb la llibreria iterator, tenim les funcions begin(arr) i end(arr)).
El for en un rang equivaldrà a:
for(auto it=estructura.begin();it!=estructura.end()++it) {
    T x=*it;
    ...
}

Funcions i funcions lambda

Això és un gran canvi a C++. En C, utilitzar funcions com a paràmetre, era molt engorrós. Havies d'utilitzar punters. En C++ la cosa millora, perquè existeixen les referències. C++11 incorpora una classe anomenada function, que serveix per facilitar més encara això. La classe en qüestió la trobem a la llibreria functional, de la STL

Potser us pregunteu per què voldríem passar una funció com a paràmetre. Realment és una cosa que s'utilitza molt. Un exemple que nosaltres veiem és amb la funció sort, de la llibreria algorithm. Aquesta té dues versions. La primera rep dos iteradors (els direm inici i fi), i ordena en el rang [inici,fi) (és a dir, fi no inclòs), utilitzant l'operador <. Què passa si el que volem ordenar no té l'operador <? Què passa si volem ordenar amb un altre ordre? Doncs la segona versió accepta un tercer paràmetre, que serà, o bé una funció, o una classe, que servirà per comparar els elements. 

Una classe? Per què?

Utilitzar una classe té els seus avantatges. A la mateixa llibreria functional tenim definides les classes greater, greater_equal, less i less_equal. Aquestes classes utilitzen templates, de manera que tu, si vols ordenar un vector creixentment, simplement pots fer
sort(v.begin(),v.end(),greater<int>());
I no ens cal crear la funció per comparar. De la mateixa manera, podem fer greater_equal, less_equal... Això està millor explicat a l'article sobre la STL

Com utilitzem la classe function?

Fàcil. Imaginem que volem fer un mergesort, que ordeni un vector d'enters utilitzant el criteri que ens diuen. Necessitem que la funció rebi dos enters, i retorni un booleà. Fem això
void mergesort(vector<int>& v, function<bool(int,int)> cmp);
En els moments de la comparació, faríem
if(cmp(...))...
Tal qual. Ara, això és una millora, però no afegeix res que no poguéssim fer abans, simplement ens facilita la vida. El que de debò és una millora són les funcions lambda

Funcions lambda

Imaginem que volem fer un programa que llegeixi un vector, i un número, i depenent d'aquest número, ordena d'una manera o d'una altra. Aquí farem que si llegeix -1, ordena normal. Si llegeix -2, ordena en ordre invers. Si llegeix -3, ordena de manera que sigui creixent mirant els valors absoluts. Finalment, si llegeix -4, ordena com amb -3, però amb l'ordre invers. Un programa per fer-ho seria així:


bool cmp1(double a, double b) {
    return a>b;
}
bool cmp2(double a, double b) {
    return abs(a)<abs(b);
}
bool cmp3(double a, double b) {
    return cmp1(abs(a),abs(b));
}
int main() {
    int n;
    while(cin >>n) {
        vector<double>v(n);
        for(double& x:v) cin>>x;
        int ordre;
        cin>>ordre;
        switch(ordre) {
            case -1:
                sort(v.begin(),v.end());
                break;
            case -2:
                sort(v.begin(),v.end(),cmp1);
                break;
            case -3:
                sort(v.begin(),v.end(),cmp2);
                break;
            case -4:
                sort(v.begin(),v.end(),cmp3);
                break;
        }
        for(int i=0;i<n;++i) {
            cout<<(i?" ":"")<<v[i];
        }
        cout <<endl<<endl;
    }
}

Com es pot veure, tot el que utilitzo són coses que a C++98 ja existien. Però això és codi espagueti, fixem-nos que tenim allà unes funcions que ni cridem en el nostre codi, sinó que es criden des d'una altra funció. No necessitem tenir-les allà dalt, i de fet és contraproduent. Si un lector està llegint, quan arribi al cas -2, haurà de pujar fins a cmp1 per veure què fem. Amb el -3 igual, i amb el -4 igual. Imaginem que tinguéssim més casos així. Doncs encara més liat. 

I ens podem fer una pregunta: Igual que podem passar un valor directament, enlloc de passar una variable, per què no podem fer el mateix amb una funció? Per exemple, tu pots fer cout<<max(3,i). Doncs per què no hauria de poder passar la funció tal qual?

switch(ordre) {
case -1:
sort(v.begin(),v.end());
break;
case -2:
sort(v.begin(),v.end(),[](double a, double b){return a>b;});
break;
case -3:
  sort(v.begin(),v.end(),[](double a, double b){return abs(a)<abs(b);});
break;
case -4:
sort(v.begin(),v.end(),[](double a, double b){return abs(a)>abs(b);});
break;
}

Aquesta és la versió utilitzant funcions lambda. Anem per passos. 
Què és una funció lambda?
Una funció lambda és una funció anònima. Normalment no té nom, i acostuma a utilitzar-se per passar una funció com si fos un immediat
Com coi la declares?
Sí, una mica estrany. La versió més utilitzada és la que fa
[Captures](Paràmetres){Codi}
Què és això de la captura?
Una funció lambda pot capturar variables que estan al lloc des d'on la crides. Simplement li hem d'especificar quines, i si les captura per còpia o per referència
int foo(int a, int b) {
    int algo;
    cin>>algo;
    auto f=[algo](int a, int b) {return a+algo<b};
    int suma=0;
    for(int i=0;i<a;++i) {
        for(int j=0;j<b;++j) {
            if(f(i,j)) ++suma;
        }
    }
    return suma;
}
Aquí estem permetent que la funció lambda que he creat (aquí faig que f la cridi, perquè el codi queda més elegant) agafi la variable algo, i la utilitzi. 
Si vols que es capturi per referència, simplement faries
[&algo]...
Si vols establir que en general ho faci per referència, faries
[&,algo,algo2...]...
I agafaria per referència algo, algo2...
Si vols fer que en general ho faci per referència, però alguna variable concreta s'agafi per còpia, pots fer
[&,algo,...,=copia]...
També podem dir que per defecte ho faci per còpia, però ja és l'opció per defecte, i seria redundant. Com escriure unsigned int enlloc d'unsigned, o long long int enlloc de long long.
No li estàs dient què ha de retornar
No acostuma a caldre. En general, el compilador és prou intel·ligent com per saber-ho. Si la teva funció és prou complexa com perquè no ho sàpiga fer, ja se't queixarà. Aleshores caldrà utilitzar la forma
[Captures](Paràmetres) -> ret {Codi}
Aquí li dius què ha de retornar. Enlloc de ret, poses el tipus que retorna. És com molt python això
Aleshores, quan les utilitzo?
Una funció lambda té dos avantatges. El primer, que es pot escriure allà on la necessites. El segon, que pot consultar coses sense passar-li com a paràmetre. Per tant, jo les utilitzaria quan això et doni avantatge. Si necessites passar una funció com a paràmetre només en un moment, ho utilitzes. Si necessites que la funció accedeixi a diverses coses, però sense passar-li com a paràmetre

Usos des de la STL

La STL se'n beneficia molt, d'això, ja que hi ha una sèrie de funcions que reben una funció. A la llibreria algorithm podeu veure'n uns quants. Bàsicament hi ha moltes funcions que reben altres funcions, i justament allà les podem aprofitar. Si, per exemple, tenim un vector de strings, i volem ordenar-lo de manera que primer estiguin ordenats per longitud i, dins d'això, lexicogràficament, podríem fer

vector<string> v;
...
sort(v.begin(),v.end(),[](const string& s1, const string& s2) {
    if(s1.length()!=s2.length()) return s1.length()>s2.length();
    return s1<s2;
});
...

Aprendre a programar aux - La STL

Amb l'aparició de C++, apareixen dues coses que ho canvien tot. Són les classes i els templates (que ho podríem traduir a plantilles). Les classes, de fet, són el que històricament va fer que C++ s'escindís de C (i per això, inicialment, es coneixia com a "C amb classes", o "C amb classe". Però, si les classes fan que C++ s'escindeixi de C, els templates aporten a C++ la seva màgia.

Explicaré què són les classes i una mica de POO (Programació Orientada a Objectes) en algun altre article, segurament. Si a algú li interessa, a internet hi ha molta informació sobre això.

Què és un template?

Imaginem que volem crear una classe per emmagatzemar dades. Com les definim internament? Com a enter? Com a caràcters? Què passa si volem emmagatzemar dades més grans? 

Un template és la solució a aquest problema. És una plantilla que actua com si fos un tipus. Després, quan fem una instància de la classe o una crida de la funció on l'utilitzem, es substitueix el template pel tipus en qüestió. Per posar un exemple, si declarem la funció següent:

template<typename T>
T max (T a, T b) {
    return a>b?a:b;
}

Si cridem la funció max amb dos enters, ens retornarà un enter. Si la cridem amb dos double, ens retornarà un double. Per declarar-ho es fa així:

template<typename nom_tipus> tipus_retorn nom_funcio(paràmetres) {codi_funcio}

És a dir, si ho dividim en parts:

template<typename nom_tipus>
Això el que fa és crear la plantilla, que es diu nom_tipus. 

tipus_retorn nom_funcio(paràmetres) {codi funcio}
Això és simplement una declaració d'una funció. El template en qüestió estarà a algun paràmetre, i potser també a tipus_retorn

Normalment, aquestes dues parts estan separades línies diferents, com en el meu exemple

Això es pot extendre a una classe. Podem fer
template<typename T>
class Hola {
...
}:

Hi ha gent que utilitza la paraula class enlloc de la paraula typename, però jo penso que és barrejar coses. No obstant, és qüestió de gustos. Podem veure coses així, i és el mateix que abans
template<class T>
class Hola {
...
};

La STL

Aprofitant l'existència de classes i de templates, es poden crear moltes coses. Per exemple, podem crear una estructura de dades que emmagatzema dades de manera contigua (és a dir, un vector). Així que l'Alexander Stepanov va pensar que es podia fer una sèrie de llibreries incorporant funcions i classes que utilitzin aquestes templates. És a dir, igual que a C hi havia una sèrie de llibreries estàndard, aquí en farien unes quantes més aprofitant que ara hi ha noves eines. Li va dir Standard Template Library, coneguda popularment com a STL. Aquesta és suportada per tots els compiladors, i funciona a tots els sistemes operatius. És molt, molt extensa, i té moltes coses. Jo crec que si dic que no sé per què serveixen la meitat de llibreries, em quedo curt. Per tant, aquí només hi ha un resum, molt simple, amb les coses que són més conegudes. No obstant, es pot consultar tota la informació a diverses pàgines. Jo personalment en recomano dues:
http://www.cplusplus.com/reference/: Està molt, però molt ben feta. Exemples, bones explicacions... Jo la utilitzo molt
http://en.cppreference.com/w/: D'informació és bastant igual que l'anterior, però el disseny, almenys a mi, em dificulta molt la seva lectura. No obstant, és un dels documents disponibles al Jutge per fer examens i concursos, juntament amb la Xuleta d'EDA. Acostumar-s'hi, per tant, és una bona idea

Contenidors

El primer tipus són els contenidors. Com diu el nom, contenen dades. N'hi ha molts. A PRO1 se n'utilitzen un o dos, a PRO2 uns quants més, a EDA encara més...Aquí, una petita llista. Si no dic el contrari, cada un el trobem a una llibreria amb el mateix nom

pair

És, probablement, l'estructura més simple. Serveix per desar dos dades juntes. Les dades poden ser de diferent tipus. Té dos elements públics, que són first, i second. S'hi accedeix utilitzant el . per separar entre el nom de la parella i el nom de l'element. 
pair<int,int> p;
p.first=3;
p.second=4;
Té un parell d'operadors, que són el constructor i l'assignació. Podem fer
pair<int,int> p(3,4);
...
p=pair<int,int>(60,70);

Si utilitzem C++11 o posterior, podem inicialitzar-lo amb una llista d'inicialització, amb dos elements. Per exemple
pair<int,string> p={10,"hola"};

De la mateixa manera, té programades dues funcions, que són el swap (per intercanviar dues parelles), i el make_pair. El make_pair rep dos paràmetres, i retorna una parella que els conté. Per exemple

pair<sting,string> p=make_pair("Los amantes de Teruel: ","Tonta ella, tonto él");

La implementació de la parella està a la llibreria utility

vector

Típica. Ens fem un tip d'utilitzar-los. Des d'un punt de vista de les estructures de dades, sabem que s'emmagatzemen en posicions de memòria contínues (és a dir, v[1] està just després de v[0]), que permeten accés aleatori (podem accedir a l'element que vulguem) en temps constant, i insertar i eliminar elements al final en temps constant amortitzat. En canvi, al principi i al mig el temps és lineal

Constant amortitzat vol dir que, quan utilitzem el push_back, reservem més espai del que ens cal. Així, després, en podem fer uns quants més que ens surten gairebé gratis. 

Té moltes funcions i operadors, evidentment, però les que utilitzem són principalment
-begin: Retorna un iterador que apunta al primer element
-end: Un iterador que apunta a l'element "de després de l'últim". És a dir, no forma part del vector com a tal
-size: Retorna la mida
-resize: Canvia la mida del vector. Si ha d'eliminar elements, elimina els del mig
-empty: Retorna cert si la mida del vector és 0
-operador []: Serveix per accedir als elements. Li passes la posició respecte a l'inici, i et retorna una referència a l'element
-operador =: Bàsicament, el que fa a tot arreu. Copia un vector a un altre. Té cost lineal respecte la mida del vector
-front: Retorna el primer element
-back: Retorna l'últim element
-push_back: Incrementa en 1 el vector, i posa l'element en qüestió a la última posició
-pop_back: Elimina l'últim element del vector

vector<bool>

Ja que un vector, per emmagatzemar, internament utilitza un array, ens trobem amb un problema, i és que un booleà ocupa 1 byte, mentre que només necessita 1 bit. Utilitzant un array, estaríem utilitzant 8 vegades més espai del necessari. Imaginem, per exemple, que tenim un mapa 100*100, i volem desar quines posicions hem visitat. Podem utilitzar una matriu de booleans mida 100*100. Si utilitzem un array normal, ocuparem 100.000 bytes. Amb un vector<bool>, està optimitzat de manera que utilitzarà una mica més de 100.000 bits. Compte, no és despreciable, si enlloc de tenir 100*100 fos 1000*1000, comença a ser molt gran. Hi ha moltes coses del vector que aquest no té, però perquè tampoc és que calguin. Amb l'operador [], i l'inicialitzador, ja fem

list

De les estructures que s'estudien a PRO2, probablement la més inútil, al menys per mi. Com a curiositat, en el joc d'EDA de la FIB, hi havia una plantilla amb totes les inclusions de la stl que consideraven que utilitzaríem. Unes quantes ni les havíem estudiat. La list no hi era
Bàsicament és una estructura que té accés lent als elements, però per contra, insercions i eliminacions ràpides. Els elements no estan junts, sinó que té una implementació que cada element apunta a l'anterior i al  següent. És una estructura de dades recursiva. Anem a veure les seves funcions:

-begin i end: Retornen respectivament un iterador al primer i un iterador a l'últim element
-empty: retorna si està buit o no. Igual que al vector, vaja
-size: No cal dir res
-front i back: El mateix, primer i últim element
-push_front, pop_front, push_back i pop_back: El mateix que en el vector, però aquí tots tenen temps constant sempre
-insert: Aquest al vector no l'hem vist. Hi és, però no s'utilitza gaire. Té diverses versions, la més típica és la que rep un iterador i un element, i inserta l'element a la posició que li hem dit. Retorna un iterador que apunta al nou element. Les altres versions, i exemples, aquí
-erase: Ve a ser com l'insert, però al revés. Rep un iterador i elimina l'element aquell. Retorna un iterador que apunta a l'element següent. Compte, l'iterador que hem utilitzat com a paràmetre ja no existeix
-splice: Rep un iterador i una llista, i inserta tots els elements de la llista a continuació de l'element apuntat per l'iterador

Com a curiositat, en C++98, la funció size trigava temps lineal. En C++11, algú va pensar que, sent tan fàcil fer que tingui temps constant, es podien fer les coses bé 

deque

Ve a ser un vector on podem insertar en temps constant a l'inici, a més de poder-ho fer al final. El nom és un acrònim de "double ended queue". Una cua és una estructura de dades que només permet insertar al final. Aquesta permet també al principi i, a més, permet accedir als elements com un vector
En essència, té el mateix que un vector, afegint-li el push_front i el pop_front, que existeixen també al vector, però allà amb temps lineal

queue

Suposo que tocava, no? Aquesta és una estructura de dades tipus FIFO (acrònim de First In-First Out, és a dir, que el primer element insertat és el primer extret. S'utilitza per programar l'algorisme de Cerca en Amplada, o BFS, entre altres coses. Les seves funcions són:

-empty i size: Ja les coneixem
-front: primer element
-back: últim element. Si utilitzes una cua, no t'hauria de caldre utilitzar-lo, però hi és
-push: Inserta un element al final
-pop: Elimina el primer element

priority_queue

Aquesta és una estructura curiosa. És com una cua, és a dir, només podem eliminar el primer element, i accedim només a ell. No obstant, es manté ordenada, de manera que l'element insertat no va al final. Normalment ordena utilitzant l'operador <, i utilitza un vector per emmagatzemar les dades, però això no cal que sigui així. Podem dir-li que ho ordeni de diferent manera, o que ho emmagatzemi d'una altra manera. Només cal que permeti accés aleatori, operacions front(), push_back() i pop_back(). Si no utilitzem el vector, normalment és per utilitzar una deque, tot i que es poden utilitzar també llistes

La cua de prioritat s'utilitza per programar l'algorisme de Dijkstra, entre altres coses
Té les mateixes funcions que la cua, ja que realment, és una cua. No obstant, cal veure com la declarem.

Tenim la declaració típica, que és, si volguéssim fer una cua de prioritat d'enters
priority_queue<int> pq;
Després, si volem que ordeni diferent, haurem d'utilitzar la declaració completa. Li hem de passar el tipus, el contenidor, i l'ordenació. Un exemple, si volem utilitzar una cua de prioritat ordenada amb l'operador greater, podem fer
priority_queue<int,vector<int>,greater<int>()> pq;

stack

Aquesta és una estructura de dades tipus LIFO (Last In-First Out). L'últim element insertat és el primer que surt. Té exactament les mateixes funcions que la cua, però per accedir al primer element, enlloc de tenir la funció front, tenim la funció top, i no podem accedir a l'element de sota

Internament, per emmagatzemar les dades locals de les funcions, s'utilitza una pila. Per això, si fem una funció recursiva que no arriba mai al cas base, surt l'error "stack overflow". Si, per altra banda, programem una funció en assemblador i no ho fem bé, podem tenir un error que sigui "stack underflow"

set

Aquest es correspon als conjunts matemàtics. No permet repeticions, i els elements estan ordenats. Les insercions tenen cost logarítmic, igual que les cerques. Es pot utilitzar per moltes coses, a vegades s'utilitza per emmagatzemar si un element ha aparegut o no. No obstant, hi ha mètodes millors per fer això.
Internament utilitza un arbre binari de cerca, per això qualsevol cosa que impliqui trobar un element té cost logarítmic

Les seves funcions són:

-begin i end: Igual que a totes les altres estructures de dades
-empty i size: El mateix
-insert: inserta un element. La versió típica rep un element, i retorna una pair<iterator,bool>. L'iterador apunta a la posició on està aquell element, mentre que el booleà ens diu si l'hem insertat (true), o si ja hi era (false). Aquesta versió té temps logarítmic. Hi ha una versió que rep també un iterador que fa de pista, de manera que, si la pista és bona, la funció té temps constant amortitzat
-erase: Elimina l'element indicat. Pot rebre un iterador que apunta al valor següent a l'eliminat, o rebre un element i retornar la mida del conjunt resultant
-find: retorna l'iterador on està l'element que li passem, si hi és. Si no hi és, apunta a l'end

multiset

El mateix que el set, però pot contenir elements repetits. Aquest es troba també a la llibreria set

map

El map, també conegut com a diccionari, és una estructura similar al set. Té una clau i un valor associat a aquesta. Està ordenat en funció de les claus, i no hi poden haver repeticions (de claus). Se'l coneix com a diccionari, perquè és bàsicament el mateix. Un diccionari seria un map<string,string>, on la clau fos la paraula, i el valor la definició.
El map conté bàsicament les funcions del set, amb algunes modificacions, i alguna cosa afegida. Les insercions segueixen tenint cost logarítmic, i el find també.

Si volem insertar a un map, podem fer-ho de dues maneres. Suposem que tenim un map<T1,T2> m. Doncs la primera seria utilitzar l'insert, que espera rebre una pair<T1,T2>. Podríem fer, per tant, una de les següents
m.insert(pair<T1,T2>(clau,valor));
m.insert(make_pair(clau,valor));
m.insert({clau,valor});//Només C++11
Notem que a la tercera versió utilitzo una llista d'inicialització, que apareixen a C++11. Em sembla la manera més elegant

Enlloc d'utilitzar un insert, podem utilitzar l'operador []. Seria una cosa així:
m[clau]=valor;
Notem que, mentre que en un vector i en un array el que passem és la posició, aquí li passem la clau de l'element al que volem accedir. De la mateixa manera que un insert o un find, té cost logarítmic

La diferència fonamental és que, en el cas de l'insert, es comporta com l'insert d'un set. Si ja hi ha un element amb la clau que li posem, no canviarà res, i el booleà de la parella que ens retorna serà false. En el cas de l'operador [], si la clau hi és, la modifica, mentre que si no hi és, la crea.

multimap

Aquest és com el map, però permetent elements duplicats. Es troba a la llibreria map

unordered_set

Aquest és com un set, però sense ordre (sí, capità obvi). Internament utilitza una taula de hash. L'avantatge que té respecte al set, és el cost. El desavantatge, doncs suposo que està clar, que no està ordenat. Els costs de les insercions, eliminacions i cerques és constant en el cas mitjà, i lineal en el cas pitjor. Aquesta apareix amb C++11, així que amb versions anteriors, no es pot utilitzar

unordered_multiset

Només cal dir que el trobem a la llibreria unordered_set

unordered_map

Sembla també clar, no? 

unordered_multimap

El mateix, es troba a la llibreria unordered_map

Llistes d'inicialització

En C, per emmagatzemar dades utilitzàvem arrays. Fèiem una cosa així:
int arr[10];
I ja podíem emmagatzemar 10 enters. No obstant, no era la única manera de declarar-lo:
int arr[10]={0,1,2,3,4,5,6,7,8,9};
int arr[]={0,1,2,3,4,5,6,7,8,9};

La primera versió donava la mida i el contingut que havia de tenir. La segona només el contingut, ja que el compilador és prou hàbil per calcular la mida. 

Això a C++98 no va ser exportat pels contenidors de la STL. No podíem fer
vector<int> v={0,1,2,3,4,5,6,7,8,9};
Per sort, quan va arribar C++11, es van adonar de l'error, i van decidir arreglar-ho. Van crear les llistes d'inicialització. 

La gràcia de les llistes d'inicialització és que es creen automàticament, només fent {e1,e2,...}. A més, tenen conversió a bastants contenidors de la STL, així com als nostres propis. Així, podem fer

set<int> primers_menors_de_deu={2,3,5,7};

A més d'això, s'analitzen recursivament. Una llista d'inicialització pot contenir-ne una altra dins. Així, podríem fer
vector<pair,int,int>> v={{1,2},{3,4},{5,6},{7,8}};
map<string,int> m={{"Hola",3},{"Adeu",4},{"Vint-i-cinc",25}};
A més, mentre que a C això servia per inicialitzar al moment de la declaració i ja, en C++ podem fer moltes altres coses

map<int,int> m;
...
int a, b;
cin >>a>>b;
m.insert({a,b});

int random(int min, int max) {
    return min+rand()%(max-min);
}
pair<int,int> parella_aleatoria(int min, int max) {
    return {random(min,max),random(min,max));
}

I moltes més coses que se'ns poden acudir. 

Per altra banda, podem fer coses així:

struct S{
    int a, int b;
    string s;
};
...
struct S={1,2,"Hola"};

o fins i tot

struct Salts {
    string saltador;
    double alcada;
};
...
vector<Salts> grans_salts={{"Stefka Kostadinova",2.09},{"Javier Sotomayor",2.45},{"Luís Carrero Blanco",numeric_limits<double>::infinity()}};

Iteradors

Hem vist que totes les estructures de dades anteriors, excepte la parella, el vector de booleans, pila i cua (normal i de prioritat), tenen iteradors. Però, realment què és un iterador?

Per entendre la necessitat dels iteradors, hem de saber com s'emmagatzemen les estructures de dades internament. Només ens interessaran les que disposen d'iteradors, ja que és el que volem veure

vector: s'emmagatzemen els elements en posicions de memòria seguides. Dins de la classe hi ha un atribut que és un punter que apunta al primer element. Si cada element té mida N, per accedir a v[i] només cal accedir a @v+N*i.
List: cada element consta d'un valor, un punter a l'element anterior, i un punter a l'element següent. Una cosa així:
struct Element {
    T valor;
    Element* node_ant, node_seg;
};
Aleshores, si volguéssim accedir a l'element ièssim, hauríem de fer un bucle que anés recorrent elements, des del primer fins a l'últim. Això faria que tingués cost lineal respecte i, cosa que fa que necessitem una manera d'emmagatzemar quin element estem visitant. Una idea seria utilitzar un punter que hi apunti
Set i map: Normalment s'implementen utilitzant arbres binaris de cerca. Per recórrer és senzill, però saltar d'un element a un altre és una cosa molt més complexa i, a diferència de la llista, no li podríem deixar a l'usuari de la classe
Unordered set/map: S'implementen utilitzant taules de hash. No entraré molt al tema, ja que és un tema complex

Aleshores, tenim una sèrie d'estructures de dades, que internament s'implementen de maneres molt diferents. I volem fer algorismes genèrics per ells. Així que necessitem un mètode per iterar pels elements de l'estructura, que funcioni igual a tots (si més no, amb els mateixos operadors). Per això s'inventen els iteradors.

Un iterador és una manera de recórrer una estructura de dades. Totes les estructures de dades tenen una funció begin, que retorna un iterador que apunta al primer element, i una funció end, que retorna un iterador que apunta a l'element "de després de l'últim". És com una manera de delimitar l'estructura, quan un iterador arriba a ser igual a l'end, ja has acabat.

Un iterador, realment, és molt similar a un punter. El que es fa és canviar-li una mica els operadors. En el cas dels punters, l'operador ++ passa a apuntar a l'element que està a la següent posició de memòria, mentre que en els iteradors, això només es compleix amb el vector i la deque. Per tant, hem de sobrecarregar aquests operadors, de manera que

Existeixen diferents tipus d'iteradors:

Input/output: l'iterador més bàsic. Si és d'entrada, permet accedir al valor, i ja està. Si és de sortida, accedir-hi per modificar-lo.
Forward: Compleix el que compleixen els anteriors, i permet recórrer l'estructura de dades, però només endavant.
Bidirectional: Compleix el que compleixen els anteriors, i a més permet anar enrere
Random Access: A més de complir el que compleixen els anteriors, permet sumar-li i restar-li números, comparacions de desigualtat (mirar si un és menor que un altre, per exemple), i l'operador []

Per accedir a un element, utilitzem l'operador de desreferència *, com en els punters. Si conté una estructura (una pair, per exemple), podríem utilitzar també l'operador ->, per accedir a cada un dels elements de l'estructura. Per desplaçar l'iterador endavant, utilitzem l'operador ++, mentre que per fer-ho enrere, l'operador --
Aleshores, imaginem que tenim una llista, i volem incrementar els seus elements. Podríem fer:

for (list<int>::iterator it=l.begin();it!=l.end();++it) {
    ++(*it);
}

Si tenim un map i volem incrementar els valors, per exemple, podríem fer

for(map<int>::iterator it=m.begin();it!=m.end();++it) {
    ++it->second;//Un map conté, en essència, parelles d'elements
}

Si volguéssim mostrar-ho, en canvi, faríem
for(map<int>::const_iterator it=m.begin();it!=m.end();++it) {
    cout<<it->first<<' '<<it->second<<endl;
}

Posant que sigui un iterador constant, fem que no permeti modificar l'estructura de dades. Això serveix per posar-nos una autolimitació, si ens equivoquem i en algun moment ho modifica, tindrem un error

Com podem veure, els iteradors ens permeten tenir algorismes més genèrics. Un algorisme aplicable a un vector, en general ho serà també a una llista (a no ser, clar, que necessiti un iterador Random Access, que la llista no pot proporcionar).

Algorismes:

Hi ha molts algorismes, molt útils. Tenim les ordenacions, cerca dicotòmica, el garbell d'Erastostenes... Imagineu que cada cop que volem ordenar un vector, haguéssim de programar una ordenació. Ens passaríem la vida programant coses que no són les més importants. Per això s'han programat diversos algorismes a la STL. Els més importants els trobem a la llibreria algorithm. Faré una petita llista dels que he trobat

for_each: Aplica una funció a un rang. Això, sumant-li les funcions lambda, és una eina molt poderosa. 
find: Troba en un rang
find_if: Busca l'element que compleix la funció que li passem. Igual que el for_each, funciona molt bé amb les funcions lambda
transform: Rep un rang, i un iterador que apunta a l'inici d'una estructura de dades. Fa la modificació indicada, i ho deixa a partir de l'iterador indicat
random_shuffle: ordena aleatòriament un rang. Serviria per barrejar una baralla de cartes, per exemple
sort: ordena en un rang. Ja l'hem estudiat. Se li pot passar una funció o un functor (següent punt) que defineixi l'ordre
stable_sort: ordena en un rang, preservant l'ordre dels valors iguals. Això serviria si, per exemple, tenim un vector<pair<int,int>> i hem definit que ordeni segons el primer element, entre dos elements que tinguin aquest igual, conservarà l'ordre que tingués
partial_sort: ordena de manera que els elements més petits estan a la meitat inferior i ordenats, mentre que la resta estan desordenats
lower_bound: retorna el primer element del rang que no és major al que li passem (és a dir, el primer que és <=. Com que utilitza la cerca dicotòmica, cal que estigui ordenat
upper_bound: Retorna el primer element major al que li passem (és a dir, el primer >). Cal que estigui ordenat per les mateixes raons
binary_search: La cerca dicotòmica, tal qual. Retorna true si hi és, false si no hi és
merge: rep dos rangs ordenats, i els combina de manera que el resultant estigui ordenat
set_union, set_intersection, set_difference: Les tres operacions sobre conjunts
min, max: Retornen respectivament el mínim i el màxim. La gràcia és que operen sobre templates
min_element, max_element: El mateix, però en un rang

El sort, per exemple, necessita un iterador Random Access. Com que la llista no el pot proporcionar, la solució ha sigut implementar una funció pròpia dins de la llista. No sé ben bé com ho fa, però ordena una llista en temps similar al sort típic

Functors:

Realment no sé com traduir la paraula. Functor ve de l'anglès "function operator". És una classe/struct que té sobrecarregat l'operador (), de manera que actua com una funció. Un exemple seria la classe greater, té sobrecarregat l'operador () de manera que rep dos elements, i retorna true si el primer és major que el segon. Això té diversos avantatges. 

Conservar valors

const int N=5;
struct Functor {
  int valor;
    Functor(int x) {
  valor=x;
  }
    int operator()(int y) {
  return y+valor;
  }
};
int main() {
    vector<int> v(N);
    for(int& x:v) cin >>x;
    vector<int> res(N);
    transform(v.begin(),v.end(),res.begin(),Functor(5));
    for(int x:res) cout <<x<<' ';
    cout<<endl;
}

Com podem veure, aquí hem creat un Functor (que es diu així), que servirà per sumar un número a un altre. En aquest cas, utilitzem l'algorisme per sumar 5 a un vector. Podem sumar 5, 6, o qualsevol enter. 
Observem que quan cridem a transform, fem Functor(5). Aquí no cridem l'operador(), sinó que estem cridant la funció creadora (Functor(int x)). Si, un cop el tenim creat, fem () sobre ell, estarem cridant l'operador

const int N=15;
struct Functor {
    int valor;
    int sumes=0;
    Functor(int x) {
        valor=x;
    }
    int operator()(int y) {
        if(sumes++>10) cerr<<"ERROR!"<<endl;
        return y+valor;
    }
};
int main() {
    vector<int> v(N);
    for(int& x:v) cin >>x;
    vector<int> res(N);
    transform(v.begin(),v.end(),res.begin(),Functor(5));
    for(int x:res) cout <<x<<' ';
    cout<<endl;
}

Aquí imaginem que no volem cridar la funció més de 10 cops. Doncs bé, el functor mateix emmagatzema quantes vegades ha sigut cridat, de manera que no ens n'hem de preocupar

Eficiència

Si fem que una funció rebi un punter a una altra, el compilador no pot fer inlining, sinó que haurà de cridar-la cada cop que la necessiti. Recordem que cridar una funció implica una sèrie de passos en assemblador
- Desar el valor dels registres que es poden modificar en una funció, si els necessitem
- Apilar els paràmetres
- Saltar a l'inici de la funció, desant %eip i %ebp. 
- Desar el valor dels registres que la funció ha de preservar, si els necessitem
- Executar la funció
- Col·locar el resultat a %eax
- Recuperar el valor dels registres desats
- Recuperar el valor de %ebp
- Retornar on estàvem, recuperant el valor de %eip
- Recuperar el valor dels registres desats
- Seguir amb l'execució del programa

Hi ha funcions on fer tot això és absurd. Per exemple, una funció on el que farem és retornar a>b, no cal fer tot això. Inlining vol dir agafar el cos de la funció, i substituir-lo a cada lloc on se la crida, de manera que no calgui fer tots aquests passos. Normalment el compilador ho fa amb les funcions que veu que és absurd cridar-les així. Se li pot aconsellar al compilador que ho faci, posant la paraula "inline" davant de la declaració de la funció. 

El problema és que, quan passem una funció com a paràmetre d'un altre, el que estem fent és passar un punter a aquella. I el compilador es troba que no pot fer-ho. Per això, la std, enlloc d'utilitzar funcions per comparar, utilitza functors. Això permet que una funció que utilitza less<T> per comparar, es comporti igual que una que utilitzi l'operador <, ja que realment, ho està fent

Un petit resum

Tenim uns quants functors a la llibreria functional. Aquí, un petit resum:

bit_and: Retorna a&b
bit_or: Retorna a|b
bit_xor: Retorna a^b
divides: Retorna a/b
equal_to, greater, greater_equal, less, less_equal: Retornen a==b, a>b, a>=b, a<b i a<=b, respectivament
logical_and, logical_not, logical_or: Retornen a and b, not a i a or b, respectivament
minus: a-b
modulus: a%b
multiplies: a*b
negate: -a
not_equal_to: a!=b
plus: a+b

Un exemple de l'us seria:

struct Persona {
    int edat, alcada;
    string nom;
    friend bool operator>(const Persona& a, const Persona& b) {
        if (a.edat!=b.edat) return a.edat>b.edat;
        if (a.alcada!=b.alcada) return a.alcada>b.alcada;
        return a.nom>b.nom;
    }
};
int main() {
  vector<Persona> v(N);
  for(Persona& x:v) cin >>x.nom>>x.edat>>x.alcada;
    sort(v.begin(),v.end(),greater<Persona>());
  for (Persona x:v) cout <<x.nom<<' '<<x.edat<<' '<<x.alcada<<endl;
}

Aquí tenim una struct Persona, que té definit l'operador >. Això ens permet utilitzar el functor greater per ordenar. 

Aprendre a programar 10 - Algorismes fonamentals



Aquesta és la part de PRO1 a la que la gent fa menys cas. De fet, quan ho vaig fer, vaig passar olímpicament d'ella. No obstant, últimament els ha donat per posar alguna pregunta relacionada a l'últim examen, així que potser millor saber-ne una mica. Tampoc és que siguin gaires, i uns quants ja estan explicats a l'article anterior.



Producte ràpid:

Imaginem que tenim un sistema que només pot fer sumes, restes, productes per 2, i divisions per 2. Com podem implementar un algorisme que multipliqui números de manera més o menys ràpida?


Primera idea:

int prod (int a, int b) {
    int r=0;
    for (int i=0;i<a;++i) r+=b;
    return r;
}


Si mirem el seu cost, farà a iteracions. És a dir, temps Θ(a). Lineal respecte a.
Què passaria si la cridem pel cas a=2^31-1, b=1? Doncs que ho podríem haver fet en una iteració, però ho farem en moltes. Podríem pensar de millorar-ho fent



int prod (int a, int b) {
    int r=0;
    if (a<b) for (int i=0;i<a;++i) r+=b;
    else for (int i=0;i<b;++i) r+=a;
    return r;
}


No obstant, això segueix tenint temps lineal. Podem optimitzar-ho?

Versió logarítmica

int prod (int a, int b) {
    if (b==0) return 0;
    if (b%2==0) return prod (a*2,b/2);
    return prod(a,b-1)+a;
}


Anem a mirar això. Si b==0, a*b=0. Si b és parell, a*b=(a*2)*(b/2). I això el que fa és que, sempre que b sigui parell, estarem dividint entre dos.

Anem a analitzar el cost. Quin és el cas millor? El cas millor és quan b sigui 2^n. En aquest cas, necessitarem n+1 crides. És a dir, temps logarítmic. I quin és el cas pitjor? Quan sigui (2^n)-1. Aquí, la meitat de crides faria la resta, i l'altra meitat la divisió. No obstant, això segueix sent temps logarítmic, ja que, malgrat que fem el doble de crides, Θ(2*log b)=Θ(log b). 

Se'n pot fer una versió iterativa, la teniu a l'arxiu d'algorismes fonamentals de PRO1

Exponenciació ràpida:

Aquesta idea la podem extendre a la potència. 

int exp(int a, int n) {
    int r=1;
    for (int i=0;i<n;++i) r*=a;
    return r;
}

Aquest és l'algorisme que faríem intuitivament. No obstant, això té cost Θ(n). En canvi, sabem que a^n=(a^(n/2))^2. Per tant, aprofitant això, podem fer

int exp(int a, int n) {
    if (n==0) return 1;
    int aux=exp(a,n/2);
    if (n%2==0) return aux*aux;
    return aux*aux*a;
}

Això té temps Θ(log n). 

Cerca dicotòmica

Imaginem que ens demanen fer una funció
int cerca(const vector<int>& v, int l, int r, int x);
Que rep un vector ordenat creixentment, i mira si entre l i r hi ha l'element x. Si no hi és, retorna -1. Com ho podem fer?

int cerca(const vector<int>& v, int l, int r, int x) {
    for (int i=l;i<=r;++i) {
        if (v[i]==x) return i;
    }
    return -1;
}

Ara, això ens obliga a recórrer tot el subvector. Però sabem que el vector està ordenat, per tant arribarà un moment on les iteracions seran inútils, ja que l'element no pot estar allà. Podem fer

int cerca(const vector<int>& v, int l, int r, int x) {
    for (int i=l;i<=r;++i) {
        if (v[i]==x) return i;
        if (v[i]>x) return -1;
    }
    return -1;
}

Això el que fa és que, a la que ens hem passat la posició per on hauria d'estar, retornem -1, perquè no hi pot estar. No obstant, això segueix sent lineal, no? No podem fer per dividir els casos d'alguna manera?

Podem aprofitar-nos d'una cosa. Si volem mirar entre l i r, podem calcular el punt mig ((l+r)/2), i mirar si l'element està allà. Si hi és, ja hem acabat. Si no, mirem si és menor o major. Si l'element que hi ha a aquesta posició és menor que el que busquem, vol dir que està a la dreta. Si no, a l'esquerra. Una obvietat, no? Però, si ens hi fixem, hem dividit per dos la mida del subvector on volem buscar.

És a dir, per trobar x en un vector de mida n, tindrem:

x està en vector de mida n?
x està en vector de mida n/2?
x està en vector de mida n/4?
x està en vector de mida n/8?
...
x està en vector de mida 1?

És a dir, si no hi és, farem log2(n)+2 accessos al vector. En canvi, amb una cerca lineal, faríem n accessos. Per un vector de mida 100.000, amb la cerca dicotòmica necessitarem, com a molt, 19 accessos amb la dicotòmica. Si ho fem amb la lineal, podem veure quants en farem. No cal parlar ja de si hi ha 1.000.000, o 1.000.000.000 d'elements (42 accessos amb la dicotòmica). La mida màxima d'un enter és 2.147.483.647, amb 33 accessos podem comprovar si un element hi és

Podem fer el codi. Queda una cosa similar

int cerca(const vector<int>& v, int l, int r, int x) {
    if (l>r) return -1;
    int mig=(l+r)/2;
    if (v[mig]==x) return mig;
    if (v[mig]<x) return cerca(v,mig+1,r,x);
    return cerca(v,l,mig-1,x);
}

De fet, podem analitzar la complexitat d'això. El cas pitjor serà quan no hi sigui, que el que farà és anar dividint, fins que arribi un punt on l>r, i retorni -1. És a dir, la distància entre l i r s'anirà dividint entre 2, fins que sigui 0, una crida més, i ja estarà. Això és temps Θ(log n)

Suma de vectors dispersos

Quan tenim un vector on molts dels seus elements són 0, utilitzar un vector vol dir malgastar molt espai. Una cosa que podem fer és crear un vector dispers. En aquest vector, tots els elements són una parella de dos elements. El primer membre és la posició, i el segon el valor. Així, el primer vector es convertiria en el segon

0 0 0 0 0 30 0 0 0 0
{5,30}

Com implementem un algorisme que sumi dos d'aquests vectors?

vector<pair<int,int> > suma_dispersa (const vector<pair<int,int> >& v1, const vector<pair<int,int> >&v2) {
    int n, m;
    n=v1.size();
    m=v2.size();
    int i=0,j=0;
    vector<pair<int,int> > r;
    while (i<n and j<m) {
        if (v1[i].first<v2[j].second) {
            r.push_back(v1[i++]);
        }
        else if (v1[i].first>v2[j].second) {
            r.push_back(v2[j++]);
        }
        else {
            r.push_back(pair<int,int>(v1[i].first,v1[i].second+v2[i].second));
            ++i;
            ++j;
        }
    }
    return r;
}

Suposo que s'entén el que faig, no?

Producte de matrius

typedef vector<int> Fila;
typedef vector<Fila> Matriu;

void producte(const Matriu& A, const Matriu& B, Matriu& C) {
    C=Matriu(A.size(),Fila(B.size(),0));
    for (int i=0;i<int(A.size());++i) {
        for (int j=0;j<int(B[0].size());++j) {
            for (int k=0;k<int(B.size());++k) {
                C[i][j]+=A[i][k]*B[k][j];
            }
        }
    }
}

Aquest algorisme seria el que se'ns vindria al cap més intuitivament. Bàsicament és programar el mateix que fem per multiplicar dues matrius a mà. No obstant, no és la millor versió per multiplicar matrius. Si volem millorar-ho una mica sense gaire esforç, podem fer-ho així:

void producte(const Matriu& A, const Matriu& B, Matriu& C) {
    C=Matriu(A.size(),Fila(B.size(),0));
    for (int k=0;k<int(B.size());++k) {
        for (int i=0;i<int(A.size());++i) {
            for (int j=0;j<int(B[0].size());++j) {
                C[i][j]+=A[i][k]*B[k][j];
            }
        }
    }
}

Bàsicament perquè, per temes de memòria cache, és molt més eficient recórrer una matriu per files que per columnes. I com podem veure, a la primera versió, per cada execució del bucle intern, estem recorrent A per files, B per columnes, i C no canvia. En canvi, amb la segona versió, recorem C i B per files, i A no canvia. 
No obstant, les dues versions triguen temps cúbic. Si cal una multiplicació més ràpida, caldria utilitzar l'algorisme de Strassen

Trobar l'arrel d'una funció continua 

Suposo que tothom coneix el teorema de Bolzano. És aquell que diu
Sigui f:[a,b] -> Reals tal que f(a)*f(b)<0, existeix c a l'interval (a,b) tal que f(c)=0
Cosa que, expressada en un llenguatge més col·loquial, ve a ser que si una funció continua en un interval és positiva en un punt de l'interval, i negativa a un altre, en algun moment val 0. Una obvietat, potser, però una obvietat important. Perquè permet trobar una solució d'una funció amb molta precisió. 

double arrel(double a, double b, double epsilon) {
    if (b-a<=epsilon) return a;
    double c=(a+b)/2;
    if (f(a)*f(c)<=0) return arrel(a,c,epsilon);
    return arrel(c,b,epsilon);
}

És a dir, si la diferència entre els dos és menor que epsilon, ja hem trobat una solució. Si no, mirem què passa al mig, i en funció d'això, anem fent. 

Aprendre a programar 9 - Ordenar

Imaginem un problema que digui "donada una n, i un vector de mida n, mostra per la sortida estàndard el vector ordenat creixentment". Com ho faríem?

Una primera idea seria recórrer el vector, de manera que, sempre que trobem dos elements adjacents en ordre erroni, els intercanviem. Podem anar fent això repetidament fins que el vector estigui ordenat. Aquest algorisme és conegut com Ordenació per Bombolla, o Bubblesort

Ordenació per Bombolla - Bubblesort

void bubblesort(vector<int>& v) {
    int n=v.size();
    bool intercanviat=true;
    for (int i=0;i<n-1 and intercanviat;++i) {
        intercanviat=false;
        for (int j=0;j<n-i-1;++j) {
            if (v[j]>v[j+1]) {
                swap(v[j],v[j+1]);
                intercanviat=true;
            }
        }
    }
}

És a dir, farem n-1 passades pel vector. A cada passada arribarem fins a n-i-1. Això és perquè a la primera passada, per com està fet l'algorisme, el número més gran acabarà l'últim. A la segona, el segon més gran acabarà el penúltim. 

Afegim una variable booleana que es diu intercanviat, que la posem a true si hem fet un intercanvi. Això és perquè si en una passada sencera no fem cap intercanvi, vol dir que ja està ordenat, i ens podem aturar aquí. 

Es diu ordenació per bombolla, perquè hi ha una mena de bombolla que es va desplaçant, que és la que senyalitza quines dos posicions comparem. És a dir, donat un vector mal ordenat com el següent:

10 4 5 1 7 8 6 9 2 3

Si fem una execució pas a pas del bucle intern, quedaria

(10 4) 5 1 7 8 6 9 2 3
4 (10 5) 1 7 8 6 9 2 3
4 5 (10 1) 7 8 6 9 2 3
4 5 1 (10 7) 8 6 9 2 3
4 5 1 7 (10 8) 6 9 2 3
4 5 1 7 8 (10 6) 9 2 3
4 5 1 7 8 6 (10 9) 2 3
4 5 1 7 8 6 9 (10 2) 3
4 5 1 7 8 6 9 2 (10 3)
4 5 1 7 8 6 9 2 3 10

Això comporta un problema, conegut com "la llebre i la tortuga". Què passa quan hi ha un número gran al principi? El 10 estava al principi, i en un moment ja està al final. La llebre. Què passa quan hi ha un número petit al final? (el 2, o el 3). S'han mogut una sola posició. Són la tortuga. Per això neix una petita variació

Cocktail shacker:

Aquest algorisme el que fa és, enlloc de recórrer el vector sempre en el mateix sentit, va canviant. La gràcia és que les tortugues es converteixen en llebres, i les llebres en tortugues. Seguint des del vector d'abans:

4 5 1 7 8 6 9 2 (3 10)
4 5 1 7 8 6 9 (2 3) 10
4 5 1 7 8 6 (9 2) 3 10
4 5 1 7 8 (6 2) 9 3 10
4 5 1 7 (8 2) 6 9 3 10
4 5 1 (7 2) 8 6 9 3 10
4 5 (1 2) 7 8 6 9 3 10
4 (5 1) 2 7 8 6 9 3 10
(4 1) 5 2 7 8 6 9 3 10
1 4 5 2 7 8 6 9 3 10

Com podem veure, el 2 s'ha desplaçat des de la posició 7 a la posició 4, bastant més del que ho hauria fet amb un bubblesort normal. No obstant, aquest canvi no modifica el cost de l'algorisme

Anàlisi del cost del Bubblesort:

Analitzarem el cost en el cas pitjor. Aquest cas pitjor és quan haguem de fer totes les iteracions del bucle. 

Això fa que haguem de fer (n-1)+(n-2)+...+1 iteracions del bucle interior. Com que el bucle interior té cost constant, el cost serà Θ((n-1)+(n-2)+...+1), que és Θ(n^2)

Ordenació per selecció (Selection Sort):

Enlloc d'anar intercanviant de manera que l'element més gran es vagi desplaçant cap a la dreta, per què no buscar l'element més gran (o el més petit), i posar-lo? Aleshores el segon, el tercer, fins que arribem al final. Per exemple, donat el següent vector, i ordenant de manera que busquem el menor

10 4 5 1 7 8 6 9 2 3

El menor és l'1. El posem a davant de tot (fem un swap amb el primer element)

1 4 5 10 7 8 6 9 2 3

El següent és el 2. 

1 2 5 10 7 8 6 9 4 3

I seguim

1 2 3 10 7 8 6 9 4 5
1 2 3 4 7 8 6 9 10 5
1 2 3 4 5 8 6 9 10 7
1 2 3 4 5 6 8 9 10 7
1 2 3 4 5 6 7 9 10 8
1 2 3 4 5 6 7 8 10 9
1 2 3 4 5 6 7 8 9 10

I ja està ordenat.

A programar

Per programar, com que la versió que us donen a PRO1 és començant pel final, i posant màxims, jo també ho faré així. També té avantatges, bàsicament ens facilita la vida.

int pos_maxim(const vector<int> v, int d) {
    int pos=0;
    for (int i=1;i<=d;++i) {
        if (v[i]>v[pos]) pos=i;
    }
    return pos;
}

void ordena(vector<int>& v, int d) {
    if (d>0) {
        int max=pos_maxim(v,d);
        swap(v[max],v[d]);
        ordena(v,d-1);
    }
}

L'algorisme es diu ordenació per selecció perquè per cada posició, seleccionem quin va allà, i el posem. Senzill. Si fem l'execució pas a pas de l'algorisme, veiem com queda (l'espaiat gran és per separar el que ja tenim ordenat del que encara no)

10 4 5 1 7 8 6 9 2 3
3 4 5 1 7 8 6 9 2    10
3 4 5 1 7 8 6 2    9 10
3 4 5 1 7 2 6    8 9 10
3 4 5 1 6 2    7 8 9 10
3 4 5 1 2    6 7 8 9 10
3 4 2 1    5 6 7 8 9 10
3 1 2    4 5 6 7 8 9 10
2 1    3 4 5 6 7 8 9 10
1    2 3 4 5 6 7 8 9 10
   1 2 3 4 5 6 7 8 9 10

Podem mirar quin cost té. Per un vector de mida n, farem n+1 crides recursives. A cada una farem una crida a pos_maxim, que té cost Θ(d). Com que a cada crida recursiva d decreix en 1, tindrem que el cost serà 
Θ(n+(n-1)+(n-2)+...+3+2+1). Si fem el càlcul, sabem que el sumatori de 1 a n és (n*(n-1))/2, cosa que ens fa tenir un cost que creix en funció de Θ(n^2). És a dir, creix quadràticament 

Ordenació per inserció (Insertion Sort):

Aquest algorisme és molt similar a l'ordenació per selecció, i també a l'ordenació per bombolla. El que fem és tenir 2 índexs (i, j), i anem recorrent el vector. Quan trobem una posició i tal que v[0...i ] no està ordenat, el que fem és anar fent swaps fins que quedi ordenat. Una cosa així:

void insertionsort(vector<int>& v) {
    int j;
    int n=v.size();
    for (int i=1;i<n;++i) {
        j=i;
        while (j>0 and v[j]<v[j-1]) {
            swap(v[j],v[j-1]);
            --j;
        }
    }
}

Segueix tenint cost Θ(n^2), però és bastant més ràpid que els anteriors. 

Ordenació per fusió (Merge Sort/Mergesort):

L'ordenació per fusió parteix d'una cosa molt simple. Si tenim un vector de mida n, el podem dividir en dos subvectors de mida n/2, ordenar-los, i fusionar-los BÉ (és a dir, fusionar-los ordenats). Perquè ens fem una idea, agafant el vector d'abans

10 4 5 1 7 8 6 9 2 3

Aquest es pot dividir en dos subvectors

(10 4 5 1 7) (8 6 9 2 3)
Si ordenem aquests dos subvectors, ens quedaria
(1 4 5 7 10) (2 3 6 8 9)
I ara els podem fusionar. Per fer això, el que farem és recórrer els dos vectors, i anar col·locant el més petit primer. Subratllo l'element que consultem en cada subvector, i a sota com queda la fusió
(1 4 5 7 10) (2 3 6 8 9)
1

(1 4 5 7 10) (2 3 6 8 9)
1 2

(1 4 5 7 10) (2 3 6 8 9)
1 2 3

(1 4 5 7 10) (2 3 6 8 9)
1 2 3 4

I ja s'entén com segueix, suposo. 

A poc a poc, com coi ordenem?

Senzill. Per ordenar per fusió un vector v, de la posició l a al posició r, el que fem és

mig=(l+r)/2
ordenar(v,l,mig)
ordenar(v,mig+1,r)
fusionar(v,l,r)

Aleshores, aplicant això pas a pas

10 4 5 1 7 8 6 9 2 3

10 4 5 1 7 - 8 6 9 2 3

10 4 5 - 1 7 - 8 6 9 - 2 3

10 4 - 5 - 1 - 7 - 8 6 - 9 - 2 - 3

10 - 4 - 5 - 1 - 7 - 8 - 6 - 9 - 2 - 3

Ara ja tenim els 10 subvectors ordenats, no? Al cap i a la fi, un vector de mida 1, per definició, està ordenat. A fusionar!

4 10 - 5 - 1 - 7 - 6 8 - 9 - 2 - 3
4 5 10 - 1 7 - 6 8 9 - 2 3
1 4 5 7 10 - 2 3 6 8 9
1 2 3 4 5 6 7 8 9 10

El codi:

void merge(vector<int>& v, int l, int m, int r) {
    int n=r-l+1;
    vector<int> vec(n);
    int i=l,j=m+1,k=0;
    while (i<=m and j<=r) {
        if (v[i]>v[j]) {
            vec[k]=v[j];
            ++j;
        }
        else {
            vec[k]=v[i];
            ++i;
        }
        ++k;
    }
    while (i<=m) {
        vec[k]=v[i];
        ++i;
        ++k;
    }
    while (j<=r) {
        vec[k]=v[j];
        ++j;
        ++k;
    }
    
    for (int i=0;i<n;++i) v[i+l]=vec[i];
}

void mergesort(vector<int>& v, int l, int r) {
    if (l<r) {
        int mig=(l+r)/2;
        mergesort(v,l,mig);
        mergesort(v,mig+1,r);
        merge(v,l,mig,r);
    }
}

Merge el que fa és fusionar els dos vectors, en temps Θ(n), sent n la suma de les mides dels subvectors. Farem Θ(n) crides a aquesta funció (si ens fixem, fem Θ(2^log2(n)) crides, que dóna Θ(n)). En una de les crides, la mida dels subvectors serà n. En dues serà n/2. En quatre serà n/4. En total, farem log2(n) divisions, cosa que farà que el cost sigui Θ(n log n). Si a algú li interessa entendre per què, aquí hi ha un article

Com podem veure, el Mergesort és molt millor que el Bubble Sort, Selection Sort o Insertion Sort. Per una n=100.000, n*log2(n) dóna aproximadament 1.660.964. n^2 dóna 10.000.000.000

std::sort:

Ara ja hem après a programar algorismes per ordenar. No obstant, normalment quan necessitem ordenar un vector, no ens posem a programar-ho. La llibreria algorithm ens dóna ja una funció per ordenar un vector. Aquesta funció rep dos iteradors (no cal saber què són, a PRO1 no es fa), i ordena tot el que hi ha en el rang [primer,ultim). Aleshores, si volguéssim ordenar un vector v, del principi al final, faríem

sort(v.begin(),v.end());
I el tindríem ja ordenat. 

Per ordenar en un ordre diferent

Imaginem que volem ordenar el vector decreixentment. Com ho fem? 

La funció sort pot rebre dos o tres paràmetres. Quan en rep dos, és la versió anterior. Quan en rep tres, rep també el comparador. Aquest pot ser una classe (de fet, un functor) o una funció. A PRO1 en general el que fem és el següent:

bool cmp(int a, int b) {
    return a>b;
}
...
sort(v.begin(),v.end(),cmp);

I ja tindríem el vector ordenat decreixentment. En essència, el que farà és que quedi ordenat de manera que cmp retorni true per tots els elements adjacents. No obstant, ordenar decreixentment és una cosa tan típica, que segur que algú ho ha programat abans, no?

Efectivament, no cal programar aquesta funció. Podem utilitzar greater<T>, una classe ja definida, que l'únic que té és una funció que retorna cert si el primer element és més gran que l'altre. Així podríem fer simplement

sort(v.begin(),v.end(),greater<int>());

Per ordenar un vector de structs:

Què passa si no tenim definits operadors de comparació? Aleshores com els ordenem? Aquí tenim dues opcions. La primera és definir l'operador de comparació (amb l'operador < ja faríem). La segona, crear una funció de comparació, i utilitzar la funció que rep 3 paràmetres. Aquesta segona opció és la que s'utilitza a PRO1. Un exemple seria:

struct Punt {
double x, y;
};

void llegir_punt(Punt& p);//Suposem que està definida
void llegir_vector(vector<Punt>& vp);//També definida
void mostrar_vector(const vector<Punt>& vp);//Ídem

bool cmp(const Punt& p1, const Punt& p2) {
  if (p1.first!=p2.first) return p1.first<p2.first;
  return p1.second<p2.second;
}

int main() {
    int n;
    cin >>n;
    vector<Punt> v(n);
    llegir_vector(v);
    sort(v.begin(),v.end(),cmp);
    mostrar_vector(v);
}

L'altra opció és definir l'operador de comparació

struct Punt {
    double x, y;
    bool operator<(const Punt& p2) {
        if (x!=p2.x) return x<p2.x;
        return y<p2.y;
    }
};

Tal qual. Una struct pot tenir definides funcions i operadors propis. A partir d'aquest moment, podem comparar dos punts amb l'operador <. Podríem definir-ne d'altres, però amb aquest ja fem

Quan convé ordenar?
Imaginem que tenim un problema que ens demana "donat un vector, digues quin és l'element més gran". Podríem estar temptats de fer una cosa així:

vector<int> v(n);
llegir_vector(v);
sort(v.begin(),v.end());
cout<<v[v.size()-1]<<endl;

Però hem de posar-nos sempre en el pitjor. I el pitjor és un cas on el vector estigui molt desordenat, i sigui molt gran. Sabem que el sort té, en general, cost linearítmic (és a dir, n*log(n)). Per tant, el nostre programa tindrà aquest cost. Però, no es pot fer més eficient? Doncs sí 

vector<int> v(n);
llegir_vector(v);
int maxim=v[0];
for(int i=1;i<n;++i) maxim=max(maxim,v[i]);
cout<<maxim<<endl;

Aquí ens recorrerem el vector, per buscar el màxim. Recórrer un vector té cost lineal, cosa que fa que el nostre programa el tingui. En aquest cas, era millor no ordenar

Ara imaginem que ens demanen un programa que doni la mediana. Aquí sí que necessitaríem un vector ordenat, perquè sense ordenar-lo, ens costaria massa, i segurament seria encara més ineficient. 

I ara, la frikada

Ho sento, però aquest vídeo, per mi, és droga pura. I només és una petita mostra dels algorismes d'ordenació existents