dimecres, 7 de juny del 2017

Aprendre a programar 3 - Problemes resolts I

Aquí resoldré una sèrie de problemes del Jutge. En resoldré del curs "Learning to program", ja que no estic matriculat a PRO1 aquest any, i els que tenia de quan la feia han desaparegut. No obstant, molts coincideixen, i la llista aquesta és pública. Els enunciats els podeu consultar allà, jo explicaré quin procés segueixo per resoldre'ls, i un codi d'exemple

Si me'n demaneu d'alguna altra llista, evidentment també els puc fer. No obstant, demanaria que em paséssiu el seu codi (Pxxxx, Xxxxx, o similar), de manera que el pugui trobar fàcilment

Introducció:

Suma de tres enters - P41221


Bàsicament ens introdueix 3 números, i ens demana que els sumem. Per tant, hem de declarar els 3 números, sumar-los i mostrar aquest resultat. No té gaire misteri:

#include <iostream>
using namespace std;

int main () {
    int a, b, c;
    cin >>a>>b>>c;
    cout <<a+b+c<<endl;
}

Màxim de tres enters diferents - P52847


Hem d'escriure el número més gran dels tres. Per tant, el que hem de fer és llegir-los, i comparar-los de manera que sapiguem quin és el més gran
#include <iostream>
using namespace std;

int main () {
    int a, b, c;
    cin >>a>>b>>c;
    if (a>b) {
        if (a>c) {
            cout <<a<<endl;
        }
        else {
            cout <<c<<endl;
        }
    }
    else {
        if (b>c) cout <<b<<endl;
        else cout <<c<<endl;
    }
}

Una altra opció, utilitzant funcions ja definides, seria

#include <iostream>
using namespace std;

int main () {
    int a, b, c;
    cin >>a>>b>>c;
    cout <<max(max(a,b),c)<<endl;
}

Temperatures - P15613

Aquí tenim 3 possibilitats. Que faci calor (temperatura>30), fred (temperatura<10) o que s'estigui bé. A més, si la temperatura és >=100, hem d'avisar que l'aigua bulliria, i si és <=0 es gela

#include <iostream>
using namespace std;

int main () {
    int temperatura;
    cin >>temperatura
    if (temperatura>30) {
        cout <<"fa calor"<<endl;
        if (temperatura>=100) cout<<"l'aigua bulliria"<<endl;
    }
    else if (temperatura<10) {
        cout <<"fa fred"<<endl;
        if (temperatura<=0) cout <<"l'aigua es gelaria"<<endl;
    }
    else {
        cout <<"s'esta be"<<endl;
    }
}

L'únic a destacar és que només mirem que l'aigua bulli si sabem que la temperatura és >30 (si és més petita, no serà mai 100), i només mirem si es gela quan la temperatura és <10
Una manera de fer-ho millor seria amb constants per aquests números. Una cosa així:

#include <iostream>
using namespace std;

const int FRED=10;
const int BULL=100;
const int CALOR=30;
const int GEL=0;

int main () {
    int temperatura;
    cin >>temperatura
    if (temperatura>CALOR) {
        cout <<"fa calor"<<endl;
        if (temperatura>=BULL) cout<<"l'aigua bulliria"<<endl;
    }
    else if (temperatura<FRED) {
        cout <<"fa fred"<<endl;
        if (temperatura<=GEL) cout <<"l'aigua es gelaria"<<endl;
    }
    else {
        cout <<"s'esta be"<<endl;
    }
}

Anys de traspàs - P61634

#include <iostream>
using namespace std;

int main () {
    int any;
    cin >>any;
    if (any%4==0) {
        if (any%100!=0 or (any/100)%4==0) cout <<"YES"<<endl;
        else cout <<"NO"<<endl;
    }
    else cout <<"NO"<<endl;
}

Aquest el primer cop que el vaig fer em va costar molt, i això que crec que a la pròpia llista n'hi ha de més difícils. Suposo que perquè l'enunciat és una mica enrevessat.
El codi, bàsicament, mira si l'any és divisible per 4. Si no ho és, ja sabem que no és de traspàs. Si ho és, falta comprovar que no sigui divisible entre 100, o que si ho és, sigui també divisible entre 400. Si no és divisible per 100, ja mostrem YES (lazy evaluation, ni mirem la segona condició). Si és divisible entre 100, mirarem que també ho sigui per 400, si ho és, mostrem sí

Bucles:

Primers nombres - P37500

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<=n;++i) {
        cout <<i<<endl;
    }
}

Aquest està pensat per començar a practicar amb el bucle for. Bàsicament, fem un for per recórrer de 0 a n (inclòs), i ho mostrem

Suma de quadrats - P36668

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    int suma=0;
    for (int i=1;i<=n;++i) {
        suma+=i*i;
    }
    cout <<suma<<endl;
}

En essència, el mateix que abans. Fem una variable on desar el resultat de la suma, i un bucle on anirem calculant els quadrats i els sumarem.

Primers cubs - P69277

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<=n;++i) {
        if (i!=0) cout <<',';
        cout <<i*i*i;
    }
    cout <<endl;
}

En aquest fem similar a l'anterior també. La única cosa que pot semblar estranya és la línia "if (i!=0) cout <<','". Aquesta serveix per una cosa molt simple. Sabem que hem de separar els números amb comes, però que no n'hi ha d'haver cap al principi de tot, ni al final. Hi ha diverses maneres d'evitar que surti aquella, la més simple és mostrar-la al principi de totes les iteracions excepte a la primera

Número del revés en binari - P28754

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    if (n==0) cout <<0;
    while (n!=0) {
        cout <<n%2;
        n/=2;
    }
    cout <<endl;
}
Aquesta és la primera opció. La idea és que per convertir a binari a mà, anem fent la divisió entera entre 2, i finalment agafem els residus en ordre invers. Per tant, per tenir el nombre al revés, només cal anar fent els residus i mostrar-los. Quan n==0, ens aturem
Això, no obstant, provoca un problema si ho fem amb un while, que és que si n==0 inicialment, ja no entrem al bucle. Però això es soluciona, o bé amb un if al principi, o bé utilitzant un do while

Comptant as (1) - P97969

#include <iostream>
using namespace std;

int main () {
    char c;
    cin >>c;
    int a=0;
    while (c!='.') {
        if (c=='a') ++a;
        cin >>c;
    }
    cout <<a<<endl;
}
Llegim caràcters fins que trobem un punt, i tenim una variable on comptem quantes 'a' ens han entrat

Mitjana - P78142

#include <iostream>
using namespace std;

int main () {
    cout.setf(ios::fixed); 
    cout.precision(2);
    int i=0;
    double d, suma=0;
    while (cin >>d) {
        suma+=d;
        ++i;
    }
    cout <<suma/i<<endl;
}

Aquest va bé per introduir un parell de conceptes. El primer, com fixar quants decimals tindrà la sortida. Si vols que tingui mida n, poses
cout.setf(ios::fixed);
cout.precision(n);
I, a partir d'aquí, la sortida tindrà n decimals
L'altre concepte que podem veure és el while(cin>>d). Això ja ho vaig explicar, però aquí ho veiem en un cas pràctic. El que fa és que, mentre quedin coses per llegir, segueix llegint. A la que no en quedin, acaba el bucle, i segueix amb la resta. La idea d'això és agafar dades d'un fitxer, però també es pot fer des de la consola, simplement cal posar al final el caràcter End Of File (^D a Linux, ^Z a Windows. Per posar-los, Ctrl+D i Ctrl+Z respectivament). 
La resta del programa ja està, només queda a destacar que, quan dividim un double entre un enter, el resultat és un double. Si fossin 2 enters, el resultat seria un enter
[actualització 24/09/2017]

El campanar de la Torrassa - P19724

Aquest me'l van demanar ahir, perquè es veu que molta gent s'hi ha encallat. Com fins ara, poso el codi, i a sota tota l'explicació de la lògica que hi ha darrere del codi
#include <iostream>

using namespace std;

const int CDIA=484;//En un dia sonen 484 campanades
const int MDIA=24*60;

int main() {
    int h, m, t;
    while (cin >>h>>m>>t) {
        int campanades=CDIA*(t/MDIA);
        t%=MDIA;
        if (m%15!=0) {
            int aux=(1+(m/15))*15-m;
            t-=aux;
            m+=aux;
            if (m==60) {
                m=0;
                ++h;
                if (h==24) h=0;
            }
        }
        while (t>0) {
            if (m==0) {
                if (h==0) campanades+=12;
                else if (h==12) campanades+=100;
                else campanades+=h%12;
                campanades+=4;//els quarts
            }
            else campanades+=m/15;
            m+=15;
            if (m==60) {
                m=0;
                ++h;
                if (h==24) h=0;
            }
            t-=15;
        }
        cout<<campanades<<endl;
    }
}

Primer de tot, voldria deixar clar que jo preferiria fer-ho utilitzant funcions, quedaria molt més maco. Però com que l'estan demanant abans d'haver-les estudiat, toca fer-lo així.
Quan me'l van demanar, em van dir que "s'ha de fer sense bucles". La veritat és que això de sense bucles m'ha semblat molt estrany, ja que només per llegir ja ens cal un bucle. Però suposo que és perquè molta gent té solucions que donen Time Limit Exceeded

Per començar amb el programa, hem de tenir en compte que cada dia sonen 484 campanades. Això ho podem calcular a mà, o fer-nos un petit codi que ho calculi. I sabem que un dia són 24*60 minuts. Amb això, podem saber quantes campanades sonaran pels dies sencers. És a dir, si tenim 14450 minuts, això són 10 dies sencers i 50 minuts més. Per tant, podem saber que sonaran 4840 pels dies sencers, i les que sonin pels 50 minuts

Un cop sabem quantes han sonat pels dies sencers, queda saber quantes sonaran en el temps que queda. Com que sabem que només sonen campanades als minuts 0, 15, 30 i 45, mirem quant li falta a m pel número més proper per sobre (mòdul 60), li sumem a m, i li restem a t. I, un cop estem aquí, comença el bucle

En aquest bucle l'únic que hem de fer és mirar quantes sonen a l'hora actual, segons ens ha dit l'enunciat, i saltar a 15 minuts més endavant. 

[Fi actualització 24/09/2017]

Bucles dins de bucles:

El concepte d'aquest tipus de problemes és similar als de bucles. Simplement que enlloc de fer un bucle, en fem dos, un dins de l'altre

Potències - P79817

#include <iostream>
using namespace std;

int main () {
    int a, b;
    while (cin >>a>>b) {
        int r=1;
        for(int i=0;i<b;++i) {
            r*=a;
        }
        cout <<r<<endl;
    }
}

Aquest té dos bucles, però és molt similar als anteriors. El bucle més extern només serveix per llegir els números que elevarem, mentre que és l'intern l'important. Com que sabem que a^b=a*a*a*a*a... b vegades, podem fer-ho igual. Fem un bucle que agafi 1, i el multipliqui per a b vegades, i ja ho tenim. Aquesta és la versió fàcil per calcular potències, que també és la més lenta

Logaritmes - P90133

#include <iostream>
using namespace std;

int main () {
    int b, n;
    while (cin >>b>>n) {
        int i=0,aux=1;
        while (aux*b<=n) {
            aux*=b;
            ++i;
        }
        cout <<i<<endl;
    }
}

Per fer aquest, hem de conèixer la definició del logarítme. Sabem que el logarítme en base b de n és el número al que hem d'elevar b perquè doni n. Per tant, el que fem és anar multiplicant (igual que abans) sense arribar a passar-nos, i comptem quants cops hem multiplicat. 

Triangle - P29973

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<n;++i) {
        for (int j=0;j<i+1;++j) {
            cout <<'*';
        }
        cout <<endl;
    }
}

I aquí comencem amb els problemes de dibuixar. Són problemes que, o t'encanten, o els odies, sense terme mig. 
En aquest cas el problema no és excessivament difícil ni hem de tenir en compte massa coses. Quan arribeu a "triangles bufons", "octògons facilets" i altres similars, ja veureu com de difícils poden arribar a ser
Aquí l'únic que hem de fer és dibuixar n files, a la fila 1 li posem un asterisc, a la fila 2, dos asteriscs... Per tant, com que hem començat a comptar pel 0 (podeu començar per l'1 si voleu, cap problema), el for intern fa i+1 iteracions. 

Rombe - P72484

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<2*n-1;++i) {
        if (i<n) {//Creixent
            for (int j=0;j<n-i-1;++j) {
                cout <<' ';
            }
            for (int j=0;j<2*(i+1)-1;++j) {
                cout <<'*';
            }
        }
        else {//Decreixent
            for (int j=0;j<i-n+1;++j) {
                cout <<' ';
            }
            for (int j=0;j<2*(2*n-i-1)-1;++j) {
                cout <<'*';
            }
        }
        cout <<endl;
    }
}

Aquest ja és una mica més complicat. El bucle extern no té cap misteri, ja que fem les iteracions que ens diuen. Sabem que, quan i<n, serà quan anem creixent (és a dir, cada fila és més ampla que l'anterior). I quan no, estarem decreixent. 
El cas creixent és fàcil d'explicar. Mostrem n-i-1 espais al principi (compteu-los, funciona així), i després, 2*(i+1)-1 asteriscs (el mateix, es poden comptar). En el cas decreixent, el mateix al revés, cada cop anem fent-los més estrets

Quadrats (1) - P24080

#include <iostream>
using namespace std;

int main () {
    int n;
    bool primer=true;
    while (cin >>n) {
        if (not primer) cout <<endl;
        for (int i=0;i<n;++i) {
            for (int i=0;i<n;++i) {
                cout <<n;
            }
            cout <<endl;
        }
        primer=false;
    }
}

Aquí simplement fem dos fors. Un per les files, i un per les columnes. Després de fer cada fila, mostrem un endl. Podem notar que la variable de control dels dos fors es diu igual. Això ho podem fer i no peta, perquè la del més intern emmascara la de l'extern.
Per últim, faig una variable booleana per saber si és el primer número que hem posat. Si no ho és, mostrem un salt de línia al principi de tot

Quadrats (4) - P35080

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<n;++i) {
        if (i!=0) cout <<endl;
        for (int i=0;i<n;++i) {
            for (int j=0;j<n;++j) {
                cout <<(n*i+j)%10;
            }
            cout <<endl;
        }
    }
}

Aquesta és la forma 1 de fer-lo. Sabem que l'element de la fila i j serà (n*i+j)%10, perquè a la fila 0 mostrarem 0, 1, 2..., n-1, a la fila 1 n, n+1, n+2... Podem fer-ho així, en cada moment calculem què hem de mostrar
#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<n;++i) {
        if (i!=0) cout <<endl;
        int aux=0;
        for (int i=0;i<n;++i) {
            for (int i=0;j<n;++j) {
                cout <<aux;
                ++aux;
                aux%=10;
            }
            cout <<endl;
        }
    }
}

Aquesta és una altra opció, simplement tenim una variable on posem què mostrarem, i l'anem incrementant cada cop

Cuadrats amb distància mínima a diagonals i costats - X13464

#include <iostream>
#include <cmath>

using namespace std;

int main(){
    int n;
    bool primer=true;
    while (cin >>n) {
        if (not primer) cout <<endl;
        for (int i=0;i<n;++i) {
            for (int j=0;j<n;++j) {
                int dist_vert=min(i,n-i-1);
                int dist_horit=min(j,n-j-1);
                int dist_diag1=abs(i-j);
                int dist_diag2=abs(n-i-j-1);
                cout <<min(min(dist_vert,dist_horit),min(dist_diag1,dist_diag2))%10;
            }
            cout <<endl;
        }
        primer=false;
    }
}

Aquest no surt a les llistes, ni a la de "Learning to program", ni a la de PRO1. És un problema d'examen, que em va sortir a mi. He penjat el PDF i els fitxers del programa aquí.
Recordo que gairebé ningú el va saber fer. Ja van ser poques les persones que van arribar a aquest problema, menys encara les que van entendre què demanava, i no diguem ja les que ho van plasmar en un codi. No obstant, vist ara amb perspectiva, no és tan diferent dels de dibuixar (personalment, em semblen més difícils els de dibuixar figures geomètriques).

En essència, aquí hem de veure que, el que hem de fer, és calcular distàncies. El que faig jo és fer-ho separat, perquè quedi més clar (jo sóc el típic que, si he de presentar una línia de 200 caràcters, la presento, però m'odiaríeu).
-Primer calculem la distància vertical (és a dir, com de lluny està de la primera fila, i com de la última). La primera és la 0, per tant, la distància és i-0=i. La última és n-1, per tant, la distància és n-1-i. Agafem la menor de les dues
-Acte seguit la distància horitzontal. El mateix, però amb la j.
-Acte seguit, la distància a la diagonal principal. Aquesta és quan i=j, per tant, la distància serà el valor absolut de i-j. (3,1) està a distància 2 de la diagonal primària, (1,3) també
-Finalment, a la diagonal secundària. En aquesta, sabem que i+j=n-1. Per tant, la distància serà el valor absolut de n-i-j-1

Un cop ja tenim aquestes 4 distàncies, hem d'agafar la mínima de les quatre, i mostrar-la mòdul 10.

Nota: Voldria remarcar que, en un cas així, millor redirigir la sortida a un arxiu i comparar-lo amb la sortida original. El cas més gran que ens donaven aquí era n=40, i posar-te a mirar a ull si dóna bé o no acaba marejant.
Per si algú no recorda com es feia, suposant que els arxius són els d'aquest problema, i que el programa es diu program (a seques, ni extensió ni hòsties, que als sistemes Unix no cal)
$./program <sample-000.inp >sortida.txt
$diff -dB sample-000.cor sortida.txt
$./program <sample-001.inp >sortida.txt
$diff -dB sample-001.cor sortida.txt

Si ha funcionat bé, cap dels diff mostrarà res. Si ho mostra, vol dir que el nostre programa no funciona com ha de funcionar

Funcions:

Aquí simplement posaré la funció que demanin. Ni inclusions, ni main, ni res. Només el que cal. Com que són les nostres primeres funcions, no són problemes gaire estranys. 

Nota: Quan diu "iteratiu", es refereix a que es resol utilitzant bucles

Factorial iteratiu - P57474

int factorial (int n) {
    int r=1;
    for (int i=1;i<=n;++i){
        r*=i;
    }
    return r;
}

Un factorial, com sabem, és el productori des de i=1 fins a n. És a dir, n*(n-1)*(n-2)*...*3*2*1. Sabem també que 0!=1. Per tant, l'únic que cal és fer un for que vagi multiplicant els números

Màxim de dos enters - P57846

int max2(int a, int b) {
    if (a>b) return a;
    return b;
}

Màxim de quatre enters - P73231

int max4(int a, int b, int c, int d) {
    return max2(max2(a,b),max2(c,d));
}

Aquí el que fem és calcular el màxim de a i b, el de c i d, i el màxim d'aquests dos màxims. Important recordar que hem de tenir la funció max2 definida, i abans que la max4. Això és perquè tot el que utilitzem ho hem hagut de declarar abans, i si la funció max2 està declarada a sota, max4 no en coneix l'existència

Valor absolut - P96275

int absolute(int n) {
    if(a>0) return a;
    return -a;
}

Màxim comú divisor iteratiu - P88790

int mcd(int a, int b) {
    int dividend, divisor, residu;
    if (a>b) {
        dividend=a;
        divisor=b;
    }
    else {
        dividend=b;
        divisor=a;
    }
    if (divisor==0) divisor=dividend;
    while (dividend%divisor!=0) {
        residu=dividend%divisor;
        dividend=divisor;
        divisor=residu;
    }
    return divisor;
}

L'únic problema d'aquest exercici és que l'algorisme d'Euclides s'estudia més tard, i toca buscar a Google. 
No he tingut gaire ganes de programar-lo, i com que ja l'havia fet quan feia PRO1, he copiat el codi que vaig fer en aquell moment. És lleig, pesat, i probablement ineficient, com el 99% de codis que es troben a Pastebin

Capicues - P77686

bool es_capicua(int n) {
    int girat=0;
    int copia_n=n;
    while (copia_n!=0) {
        girat*=10;
        girat+=copia_n%10;
        copia_n/=10;
    }
    return girat==n;
}

Aquí, el que faig, és girar n, i comprovar que sigui igual que el girat. Si ho és, vol dir que és capicua, i si no, doncs no. 
Un detall és que faig return girat==n, enlloc de
if (girat==n) return true;
return false;
Això és perquè, al cap i a la fi, girat==n ens dóna un booleà, que és true si són iguals. Podem retornar-lo directament, i està més ben vist

Funció per als anys de traspàs - P95401

bool es_any_de_traspas(int any) {
    return any%4==0 and (any%100!=0 or any%400==0);
}

Dates vàlides - P58459

bool es_data_valida(int d, int m, int a) {
    if (m==1 or m==3 or m==5 or m==7 or m==8 or m==10 or m==12) {
        return d>0 and d<=31;
    }
    else if (m==4 or m==6 or m==9 or m==11) {
        return d>0 and d<=30;
    }
    else if (m==2) {
        if (es_any_de_traspas(a)) {
            return d>0 and d<=29;
        } 
        return d>0 and d<=28;
    }
    return false;
}

No cal explicar gaire tampoc, no? Si el mes és un mes de 31 dies, mirem que el dia estigui entre 1 i 31 inclosos. Si no, si és de 30 dies, el mateix. Si no, si és febrer, depenent de si és any de traspàs. Si no és cap d'aquests mesos, retornem false (no hi ha un mes 0, ni un mes 13, per exemple)


Al proper article, amplio una mica el que hem fet fins ara de funcions

Aprendre a programar 2: Llibreries i Funcions

A l'últim article ja vaig explicar com fer sentències condicionals i bucles, així com vam resoldre alguns problemes d'examen. En aquest, parlaré de llibreries de C++ i de funcions

Què és una llibreria?

Nota: Li dic llibreria per costum, però realment, seria més precís biblioteca. Dir-li llibreria és una mala traducció de l'anglès library, que ha calat molt endins
Què millor que un exemple? Imaginem que volem calcular el màxim de dos números. Podríem fer un codi com el següent:

if (a>b) cout <<"El maxim es "<<a<<endl;
else cout <<"El maxim es "<<b<<endl;

També, utilitzant l'operador ?:, podríem fer

cout <<"El maxim es "<<a>b?a:b<<endl;

No obstant, ja podem imaginar que posar això cada vegada que necessites el màxim fa una mica de pal. Si en un programa necessites el màxim de dues variables 100 cops, no posaràs 100 cops això. Amb aquest objectiu neixen les funcions, que les podem visualitzar com una funció matemàtica. Per exemple, la funció matemàtica
f(x)=2x-3
Per cada valor de x dóna 1, i només 1 valor de f(x). Aleshores, això ho podem extendre a la programació. Doncs bé, una llibreria, podríem dir que serveix per encapsular funcions i altres coses (variables, constants, classes, tuples...) Així, si per exemple volem calcular l'arrel quadrada d'un número, enlloc de fer un algorisme que ho faci, podem utilitzar una funció que ho faci (i que, probablement, estarà més ben feta). En C++ hi ha moltes llibreries amb funcions molt útils, i intentaré aquí explicar-ne unes quantes. 

El conveni de C deia que una llibreria era un fitxer que tenia un nom, i l'extensió .h. Aleshores, existien llibreries com stdlib.h, stdio.h, math.h... No obstant, en C++ no és així, sinó que el que es fa és que les llibreries heretades de C tenen una c minúscula davant del seu nom (cstdlib, cstdio, cmath...), i no tenen cap extensió. Per altra banda, les llibreries de C++, moltes de les quals no es poden utilitzar en C per raons que ara no venen al cas, tenen el seu nom a seques (per exemple, iostream)

Com diem al compilador que utilitzarem una llibreria?

Es fa utilitzant la directiva de preprocessador include. Les directives del preprocessador tampoc venen al cas ara, només cal saber que es posen amb un coixinet davant (#). El nom de la llibreria el posem entre < i >. Aleshores, si volguéssim incloure iostream, faríem

#include <iostream>

I sabent això, ja podem començar a parlar de les llibreries

Iostream:

Aquesta serveix per l'entrada i sortida de dades d'un programa. No utilitzarem les funcions que inclou, però sí que utilitzarem:
-std::cin
-std::cout
-std::endl

Suposo que us sonen els tres. Els utilitzem per llegir dades i escriure-les per pantalla. 
std::cin:
És el flux de dades que ens dóna les dades de l'entrada estàndard. 

std::cout:
És el flux de dades que surt per la sortida estàndard (la consola, si no li especifiquem una altra cosa). 

std::endl:
És una macro per indicar el final de la línia. En C normalment utilitzàvem el caràcter '\n', però com que en algun sistema podria canviar la manera d'indicar-lo, std::endl és més genèric

Suposo que haureu notat que estic posant un std:: a davant, però quan programem no el posem. Això és perquè utilitzem la instrucció "using namespace std;", que indica que utilitzarem l'espai de noms estàndard, si no diem una altra cosa. No cal tampoc explicar què és això, simplement cal saber que si no hi ha aquesta instrucció, cal especificar a quin espai de noms fem referència

Cmath:

És la llibreria amb funcions matemàtiques. Equival a la llibreria math.h de C, i conté una sèrie de funcions matemàtiques. Les que utilitzarem són les següents:

std::max(a,b):
Retorna el maxim dels dos paràmetres que se'ls ha passat. 
std::min(a,b):
Retorna el mínim dels dos paràmetres
std::pow(a,n):
Retorna el resultat de fer a^n
std::sqrt(a):
Retorna l'arrel quadrada d'a
std::sin(a):
Retorna el sinus d'a
std::cos(a):
Retorna el cosinus d'a
std::tg(a):
Retorna la tangent d'a
std::abs(a):
Retorna el valor absolut d'a (és a dir, li treu el signe)

N'hi ha més, com per exemple l'arcsinus, logaritmes, arrodoniments... Es poden consultar aquí

String:

Nota: No és el mateix la llibreria string, que la llibreria cstring/string.h. S'ha d'anar amb compte de no confondre-les, ja que el codi deixaria de funcionar. Aquesta llibreria és molt útil, i la utilitzarem molt.

Què és un string? Un string el podem veure com un nou tipus de variable. No és un nou tipus, realment, però ja ens entenem així. Serveix per emmagatzemar diversos caràcters. És a dir, vindria a ser una cadena de caràcters. Podem desar-hi paraules, frases... Això ja depèn de nosaltres.

Té definits tots els operadors que necessitareu. <, >, <=, >=, ==, !=, =, +... Per saber com es compara, cal saber una cosa:

Un char equival a un número. Perquè us feu una idea, la 'A' és el 65, la 'B' el 66... Això fa que, per comparar chars, 'A'<'B' (ja que 65<66). Així es comparen els chars. Com a concepte, cal recordar que les lletres minúscules es desen seguides, les majúscules també, i els números també. Això fa que les comparacions siguin en ordre lexicogràfic

Doncs bé, un string es compara com esperaríem. Si volem comparar "Hola" amb "Hei!", mirem el primer element, i veiem que són iguals. Mirem el segon, i veiem que 'e'<'o'. Per tant, "Hei!" és menor que "Hola". Per sort, algú es va dedicar a programar tots aquests operadors, i no cal que ho fem.

L'únic que ens pot semblar estrany és el +. Aquest el que fa és concatenar strings. És a dir, si fem
string s="Hola", s2="Adeu";
cout <<s+s2<<endl;

Ens mostrarà "HolaAdeu"


Nota per programadors de C: En C, com sabreu, els strings són arrays de chars. I allà, no pots fer s=s2, sinó que tenim la funció strcpy. Doncs l'operador = és el mateix, copia un string a un altre. Igual amb strcmp, que enlloc d'utilitzar-lo, utilitzem els operadors de comparació

Funcions de la llibreria string:

string(int,char):
Retorna un string on apareix el char que li hem passat, tantes vegades com indiqui l'enter
size/length:
Retorna la llargada del nostre string. En aquest cas, com que opera des del propi string, hem de fer s.size(), enlloc de size(s).
La funció size() (o length(), són equivalents) retorna un unsigned int. Compte a l'hora de comparar, a vegades millor desar-ho en una variable entera abans i que es converteixi. Davant del dubte, tinc un post que explica tot això de les conversions
operator[]:
Serveix per accedir a un element concret. s[i] accediria a l'element i del string. Quan expliqui vectors m'aturaré en això, però és important saber que els informàtics som una mica estranys i comencem a comptar per 0. La idea seria que fer s[0] ens serveix per accedir al primer caràcter, s[1] al segon, fins a s[s.size()-1], que serveix per l'últim
getline(stream,string):
Aquesta funció serveix per llegir una línia sencera. Quan fem cin >>s, llegim fins al primer separador que trobem (espais, tabulats, salts de línia...). El getline llegeix fins al primer salt de línia que troba.
On diu "stream", acostumem a posar cin, però podria haver-hi un altre flux de dades d'entrada. Un exemple
string s;
while (getline(cin,s)) {
...
}



Definir les nostres funcions

Ara entrem a la part més important de la programació. Almenys de la programació funcional. Les funcions serveixen per encapsular el nostre codi, fer-lo més llegible, més fàcil de modificar i debugar... Són molt, però molt importants. Fins ara hem fet tots els programes dins del main, però això s'acabarà. 

Com és una funció?

La declaració d'una funció segueix l'estructura següent:
T nom_funcio(T1 parametre_1, T2 parametre_2, ..., Tn parametre_n)

On T, T1, T2,..., Tn són tipus de variable (poden coincidir entre ells). La funció rebrà una sèrie de paràmetres, i els utilitzarà per retornar alguna cosa. Un exemple fàcil:

int max (int a, int b);

Aquesta serviria per calcular el màxim de a i b. 
Dins de la funció, hi ha codi normal, com el que hem fet fins ara. L'únic nou que veiem és la sentència return. Serveix per retornar coses de la funció. Per exemple, en la funció max, retornaríem el número més gran dels dos. Anem a veure-la completa:

int max(int a, int b) {
    if (a>b) return a;
    else return b;
}

Sembla fàcil, no? Si a és la més gran, la retornem. Si no, vol dir que b és més gran, o que són iguals, i per tant retornem b. 
Truc: Una funció, quan troba el return, torna directament cap al lloc on l'havíem cridat, sense seguir executant codi. Per tant, la funció anterior equival a

int max(int a, int b) {
    if (a>b) return a;
    return b;
}

Ja que, si a fos la més gran, ja no estaríem a la funció en el moment d'arribar a la segona instrucció. Aquesta segona versió és una mica més eficient que la primera, tot i que la diferència és bastant negligible 

Funcions que no retornen res:

Aquestes es coneixen també com a "procediments". Tenen diverses utilitats, que ja les anirem veient. Es representen amb el "tipus" void

void info() {
    cout <<"Hola! Soc el Patata!"<<endl;
}

Aquest seria un exemple de funció void. En aquest cas serveix per escriure informació, però es poden utilitzar per altres coses


Per cridar funcions, es fa exactament igual que amb les definides a les llibreries, simplement no cal incloure-les, perquè ja estan al nostre codi. Un exemple de programa que utilitza la funció max (que, per altra banda, ja està definida) seria:

int main() {
    int a, b;
    cin >>a>>b;
    cout <<"El maxim es "<<max(a,b)<<endl;
}


Funcions amb el mateix nom:

Imaginem que volem definir una funció que calcula el màxim de 4 elements. Una opció seria fer com en un problema del jutge, i fer que la funció es digui max4. No obstant, C++ permet tenir més d'una funció amb el mateix nom, i les distingeix pels paràmetres que reben. És a dir, el cas següent funcionaria:

int max(int a, int b) {
    if (a>b) return a;
    return b;
}

int max(int a, int b, int c, int d) {
    return max(max(a,b),max(c,d));
}

Aquí, si li demanes que calculi max(1,2,-35, 47), com que veurà que li passes 4 paràmetres, cridarà a la de sota. Aquesta cridarà a la de dalt 3 cops, i finalment retornarà el resultat.

El main

Suposo que veient això, algú ha pensat "ei, jo escrivia en el meu codi 'int main()', i després allà a dins la resta. Això té forma de funció, no?". Efectivament. En C++, tot el codi està dins d'una funció. Quan tu executes un programa, per defecte es crida la funció main. Aquesta pot rebre o no rebre paràmetres (nosaltres ho farem sense), i retorna un enter. Concretament, retorna 0 si tot ha anat bé, i alguna cosa diferent de 0 si no ha anat bé. 

"Ei, però jo no li he posat cap return. Com retorna el 0?"
Tens raó. Abans (és a dir, en C) el main tenia un return 0 al final del codi. En canvi, ara, en C++, s'assumeix que si arriba al final del main, hi ha un return 0 (ja que, si arriba al final, se suposa que ha funcionat tot). Per tant, en C++ no cal posar el return 0. No obstant, hi ha gent que el posa. 

Àmbit de les variables

Fins ara havíem dit que una variable declarada dins d'un bucle, deixava d'existir a fora. De la mateixa manera, una variable creada dins d'un bucle n'emmascara una externa. Doncs amb les funcions passa el mateix. Dins d'una funció pots declarar variables, que només existiran dins d'aquella funció. Aquestes són variables locals. De la mateixa manera, podem declarar variables que no estiguin dins d'una funció. Aquestes són les variables globals. No obstant, a PRO1 no utilitzem variables globals (jo realment els tinc molta mania). La única excepció és per declarar constants. Quan declarem una constant, aquesta és global. Per exemple

const int MIDA_MAX=100000;
...
int main() {
    ...
    if (n>MIDA_MAX) cout <<"massa gran"<<endl;
}

Aquí hem d'imaginar que, per algun motiu, ens donaran números, i no volem que siguin més grans que 100.000. Podríem posar el 100.000 al codi tal qual, però si necessitem aquest número en diversos punts, i més tard ens adonem que ens hem equivocat (per exemple, ens hem deixat un 0 i era 1.000.000), canviar-ho seria complicat. El més fàcil és crear una constant, i utilitzar-la. En C++, les constants
- Es declaren utilitzant la paraula clau "const". Mai amb la directiva de preprocessador define, com es feia en C
- S'escriuen en majúscules
- Es declaren a dalt de tot del codi




Al proper article resoldré uns quants problemes del Jutge. Per d'aquí dos, vull plantejar un problema, perquè qui vulgui el porti ja pensat.

Les Torres de Hanoi

 

Explica la llegenda que, quan Déu va crear el món, va posar 3 barres de diamant verticals. A la primera hi va posar 64 discs d'or, cada un una mica més petit que l'anterior. Allà va crear un monestir, i va donar una ordre als monjos. L'ordre era
"Heu de moure els 64 discs de la primera torre a la tercera. Però només podeu moure els discs d'un en un. Podeu utilitzar d'ajuda la segona torre. Tingueu en compte que mai un disc pot estar a sobre d'un més petit"
La llegenda també diu que, el dia que els monjos acabin de moure els 64 discs, s'acabarà el món. 

Doncs bé, el meu problema és senzill. Cal fer una funció amb la capçalera següent
void torres_hanoi (int A, int B, int C, int n);
Que mostri per la sortida estàndard (cout) com moure n discs de la torre A, a la torre C, utilitzant B com a auxiliar. 

Aprendre a programar 1: Estructures de control

En l'article anterior explicava com crear variables, llegir-les i operar amb elles. No obstant, amb tot el que he explicat fins ara, quan tens el programa, s'executen totes les instruccions del programa. Normalment no volem que això passi. En general volem que, en funció de certs paràmetres, passi una cosa o una altra. I aquí entren en joc les estructures de control.

Execució condicional: IF-THEN-ELSE

La primera estructura de control és l'if-then-else. En C++ s'escriuria així:

if (condició) {
    codi1
}
else {
    codi2
}

Això el que faria és que, si la condició és certa, executaria codi1. Si és falsa, executa codi2. La part del else, no obstant, és optativa. Aleshores, tenint això, podríem escriure el codi següent (obviaré tota la part anterior, per no escriure massa):

...
if (a>b) cout <<"Es mes gran"<<endl;
else cout <<"No es mes gran"<<endl;

Podríem llegir-ho com "Si tal, llavors qual. Si no, ...". Una cosa destacable aquí és que no he posat els '{' i '}'. Això és perquè, quan només vols que s'executi una instrucció, no cal. Per més d'una, les has de posar per marcar l'inici i el final del que vols executar si es compleix la condició. És com una manera d'encapsular el codi, i dir-li "executa tot això"

Utilitzant això, podem anar enganxant condicions, i fer coses com

if (a>b) cout <<"Es mes gran"<<endl;
else if (a<b) cout <<"Es mes petit"<<endl;
else cout <<"Son iguals"<<endl;

Important 1!

C++ és un llenguatge on el tabulat, espaiat i salt de línia no tenen gaire importància. És indiferent si tot el codi està en la mateixa columna o no, si està tabulat amb tabuladors o amb espais... No obstant, un codi com el següent:
int a, b;
cin >>a>>b;
if (a%2==0) {
if (b%2==0) {
cout <<"Els dos son parells"<<endl;
}
else {
cout <<"El segon no ho es"<<endl;
}
}
else {
if (b%2==0) {
cout <<"El primer no ho es"<<endl;
}
else {
cout <<"El segon no ho es"<<endl;
}
}

Aquest codi, malgrat teòricament és correcte (no l'he provat tampoc), és gairebé impossible de llegir. El que s'acostuma a fer és que, tot el que s'executa dins d'una condició, es tabula un cop. És més tema de gustos, però jo quan tabulo, utilitzo el tabulador amb 4 espais d'amplada. Alguns prefereixen 8, altres 3, o fins i tot 2. Hi ha gent que prefereix tabular amb espais també, tot i que jo no ho recomano gens, ja que si utilitzes el tabulador, un lector potencial del codi podrà veure'l amb l'amplada que li resulti còmode. Així, el codi anterior quedaria:
int a, b;
cin >>a>>b;
if (a%2==0) {
    if (b%2==0) {
        cout <<"Els dos son parells"<<endl;
    }
    else {
        cout <<"El segon no ho es"<<endl;
    }
}
else {
    if (b%2==0) {
        cout <<"El primer no ho es"<<endl;
    }
    else {
        cout <<"El segon no ho es"<<endl;
    }
}

Com es pot veure, molt més llegible.

Si copieu el codi, veureu que aquest justament està amb espais. Això és perquè el bloc converteix els tabuladors en un espai, i no queda com hauria.

És important també remarcar que, si bé no hi ha cap norma estricta sobre l'amplada que han de tenir els tabulats, sí que jo diria que com a mínim han de ser d'amplada equivalent a 4 espais. Hi ha altres, com Linus Torvalds (creador del nucli Linux) que no estan d'acord amb mi, com es pot veure en aquest escrit


És a dir, resumint: Els tabulats són de 8 caràcters, i punt. Si et molesta perquè s'emporta el codi massa a la dreta, és que el teu codi és puta merda, i l'hauries d'arreglar. No obstant, també coneixem la mania que té a C++, així que no lli farem gaire cas

Important 2!

Existeix un concepte molt útil, que és la "lazy evaluation". Això vol dir que, quan s'avaluen les condicions amb and i or, si en algun moment ja no necessita seguir per saber el resultat, s'atura. Com pot ser això?

Sabem que true or és cert, per qualsevol valor de x, ja que la or és certa sempre que ho sigui alguna de les dues. De la mateixa manera, false and x és fals per qualsevol valor de x. Això ho podem utilitzar per evitar-nos problemes. En un codi com el següent:

if (a/b>3) cout <<"a es mes del triple que b"<<endl;

Si tenim que b és 0, ens fallarà el programa (ja sabem què passa amb les divisions per 0). Però sempre podem fer el següent:

if (b!=0 and a/b>3) cout <<"a es mes del triple que b"<<endl;

Això el que farà és que, si b és 0, ja sap que és fals, i no caldrà dividir. 


Els bucles:

Què passa si volem executar repetides vegades una part del codi? Si sabem que ho farem 2 cops, podem copiar-ho. Però si ho volem fer n cops, no podem. Per això entren en joc els bucles. En tenim bàsicament tres. Són el while, el do-while i el for. A PRO1 només s'utilitzen el while i el for

WHILE:

L'estructura del while és molt fàcil. S'assembla molt a un if. Bàsicament és
while (condicio) {
    codi
}

I executarà codi mentre es compleixi condicio. S'ha d'anar amb compte, si condicio és sempre certa, tindrem un bucle infinit (tot i que els bucles infinits, en certes ocasions, són útils). Un exemple seria
while(x<100) ++x;
Això el que faria és anar incrementant x fins que x arribi a 100. Si ja ho és, salta a la instrucció següent

FOR:

Un for té una estructura més complexa que el while. És la següent:

for (inicialització;condició;modificació) {
    codi
}

Com que la manera més clara és un exemple, imaginem que tenim una n ja inicialitzada

for (int i=0;i<n;++i) {
    cout <<"Iteracio "<<i<<endl;
}

Això el que farà és fer 100 iteracions, mostrant a cada una el número que és. En essència, el que es fa és, el primer cop que s'entra executa la inicialització. Després, a l'inici de cada iteració (inclosa aquesta), comprova que la condició sigui certa. Finalment, quan acaba cada iteració, fa la modificació. Vindria a ser com fer

int i=0;
while (i<n) {
    cout <<"Iteracio "<<i<<endl;
    ++i;
}

No és ben bé el mateix, però ens serveix per il·lustrar l'exemple

DO-WHILE

(si te'l vols saltar, salta-te'l)
Aquest és el tercer tipus de bucle que existeix. La seva estructura és molt similar al while. Bàsicament, seria

do {
    codi
}
while (condicio);

Com podem veure, s'assembla molt. Podríem pensar que és el mateix que un while però amb la condició al final, però realment no és cert. En aquest bucle, primer s'executa i després es comprova. Això fa que, com a mínim, s'executi un cop el bucle, indiferentment de si la condició és certa o no. Això pot ser útil, ja que a vegades saps que sempre necessitaràs una iteració. Per exemple, podríem fer un codi com el següent:

char c;
do {
cout <<"Quina creus que es la inicial del meu nom?"<<endl;
cin >>c;
while (c!='O' and c!='o');
cout <<"L'has encertat!"<<endl;

Com podem veure, aniria llegint lletres fins que encerti que la lletra és la O. Aquest tipus de bucle és una mica més eficient que el while, tot i que pel que canvia, no cal tampoc preocupar-se


ÀMBIT DE LES VARIABLES

Quan tu declares una variable, el moment on la declares importa. Si la declares fora del bucle, seguirà existint quan l'acabis, mentre que si la declares dins del bucle, un cop surtis deixarà d'existir. Això és pràctic perquè potser en un bucle necessites una variable auxiliar, però fora no la tornaràs a utilitzar. Un exemple clar és la variable de control d'un for, que quan acabis ja no t'interessa per res. Per això, el codi

for (int i=0;i<100;++i) cout <<i<<endl;
cout <<i<<endl;

Donarà error al compilar, i serà pel cout de sota, ja que i ha deixat d'existir. En canvi, si el codi fos
int i=0;
while (i<100) {
    cout <<i<<endl;
    ++i;
}
cout <<i<<endl;

No donaria error, perquè i segueix existint (ja que ha sigut declarada fora)

Aleshores, si per exemple volguéssim fer un programa que dibuixés una matriu n*n amb guions (molt útil, no trobeu?), podríem fer
for (int i=0;i<n;++i) { //Bucle extern
for (int i=0;i<n;++i) {//Bucle intern
cout <<'-';
}
cout <<endl;
}

I és completament correcte. La i que utilitza el bucle extern té un valor que és independent de la del bucle intern. Quan entres al bucle intern, la de l'extern queda emmascarada, però no es perd, de manera que quan acaba, es desemmascara. 

En canvi, si volguéssim fer que cada posició i j contingui (i+j)%10, si ho féssim com abans, fallaria, ja que la i interna emmascara l'externa. Per això, normalment en els fors dins de fors s'utilitzen noms diferents

for (int i=0;i<n;++i) {
for (int j=0;j<n;++j) {
cout <<(i+j)%10;
}
cout <<endl;
}

Nota: Les variables de control dels bucles poden tenir el nom que es vulgui. Utilitzar i i j per elles és una mica costum. Normalment s'agafen, per ordre, "i, j, k, l", i si necessites més, vigila perquè potser no ho estàs enfocant bé

Altres maneres de controlar un bucle:

Existeixen altres maneres de controlar bucles. Bàsicament tenim dues instruccions. No obstant, a PRO1 no els agraden gens, i jo les evitaria 100%. Em sembla que és útil que les conegueu, i potser en algun moment els doneu utilitat, però estan vetades completament. Són les següents:
break:
Aquesta és molt clara. Un cop apareix, abandones el bucle on estàs. Un exemple:

while (n<100) {
    n+=3;
    if (n==53) break;
}

Si en algun moment n passa a valdre 53, el bucle s'acabarà en aquell moment. 
Continue:
Aquesta potser és menys òbvia. Quan apareix, el que fa és saltar directe a la següent iteració, sense acabar l'actual. Un exemple, suposem que volem fer una llista de números primers:

for (int i=2;i<n;++i) {
    if (i%2==0 and i!=2) continue;
    ...
}
Aquí, el que faria és que en el moment que i sigui parell i diferent de 2, no comprova res, ja que l'únic nombre parell que és primer és el 2. 


Un altre tipus d'execució condicional: SWITCH-CASE

Imaginem un programa que llegeix un número i, en funció d'aquest, fa una cosa o una altra. Té 10 opcions, associades als números del 0 al 9, i si el número no és aquest, mostra un missatge d'error. Podríem implementar-ho així:

if (n==0) {

}
else if (n==1) {

}
else if (n==2) {

}
...
else if (n==9) {

}
else cout <<"ERROR!"<<endl;

El problema d'això és evident, no? Massa llarg, i massa comprovacions. Si és l'opció 10, hem de comprovar 10 condicions per arribar-hi. A més, llegir-ho és molt lleig. Per això s'inventà el switch-case. Aquest seguiria el format:

switch(variable) {
    case cas1:

    case cas2:
    ...
    default:
}

El default és opcional, és el que s'executarà quan no sigui cap dels casos contemplats. Per l'exemple anterior, seria així:
switch (n) {
    case 0:
        ...
        break;
    case 1:
        ...
        break;
    case 2:
        ...
        break;
    ...
    case 10:
        ...
        break;
    default:
        cout <<"ERROR!"<<endl;
}
I suposo que es veu que és bastant més elegant. També és més ràpid, per altra banda
Una cosa a destacar és que al final de cada cas hi ha un break. Això és perquè, si no, saltaria a la següent (per un tema d'implementació interna). En l'últim no hi és perquè ens és igual que salti al següent, ja que el següent ja és fora del switch. 


Un truc molt útil:

Existeix un truc que permet llegir tants elements com ens interessi. Quan tu fas cin>>n, pots avaluar això com una condició. Serà cert sempre que quedi alguna cosa per llegir, i fals si ja no pot llegir res més. Un exemple seria, donat aquest problema:
Sembla fàcil, però va ser un dels problemes que em va sortir al primer examen. Per resoldre aquest problema, seria tan fàcil com fer:

#include <iostream>

using namespace std;

int main () {
    int a, b;
    while (cin >>a>>b) cout <<a+b<<endl;
}

I ja tindríem fet el programa. Ho podem llegir com "mentre puguis llegir dues variables, llegeix-les. i fes això. En el moment que no puguis, surt del bucle"

 El que cal tenir en compte és que entra tot el de l'esquerra seguit, i ha de sortir tot el de la dreta (idèntic, si surt una mica diferent ja no serveix). Per fer la prova del programa hi ha dues maneres:
1. Quan acabi l'entrada, poses el caràcter end of file. Aquest, en sistemes Unix o basats en Unix (com Linux), es posa amb la combinació Ctrl+D. En Windows es posa amb Ctrl+Z
2. Redirigint l'entrada i/o la sortida (això és el que fa el Jutge). Quan tu executes un programa (en Linux fent $./programa), pots dir-li que agafi l'entrada d'un fitxer, així com dir-li que enviï la sortida a un altre fitxer. Per redirigir l'entrada, es fa posant <entrada.txt, i per redirigir la sortida, >sortida.txt. El Jutge et dóna ja fitxers amb l'entrada i la sortida que espera, perquè ho puguis fer fàcilment. Per exemple, en el cas del problema d'abans, l'entrada es deia sample-000.inp, i la sortida correcta sample-000.cor. Per executar, faríem:

$./programa <sample-000.inp >sortida.txt
I ens quedaria la sortida que dóna el nostre programa al fitxer sortida.txt (compte, si ja existia, el sobreescriurà). Per veure si ho hem fet bé, faríem
$diff -dB sortida.txt sample-000.cor
I si no ens mostra res, vol dir que està bé. Si ens mostra coses, vol dir que el nostre programa no fa el que ha de fer. 

Anem a programar una mica més

Aquest és el segon problema que em va sortir a l'examen. Anem a fer-lo pas a pas:

Primer de tot, veiem que tenim una línia d'enters, dels quals no sabem la quantitat. Per tant, hem de posar un while(cin>>n).
Veiem que hem de saber quants positius hi ha, quants negatius, i quants en total. Ho podem fer de diverses maneres. 
-Podem desar quants són positius, quants negatius i quants 0
-Podem desar quants són positius, quants negatius i quants han entrat en total
I altres...
Jo triaré guardar els positius, els negatius i el total. Aleshores, en el cos del bucle hauríem de mirar si és positiu, i si ho és, incrementar pos. Si no, si és negatiu, incrementem neg. I, sigui com sigui, incrementem sempre tot. 

Aleshores, el programa seria

#include <iostream>

using namespace std;

int main () {
    int pos, neg, tot;
    pos=neg=tot=0;
    int n;
    while (cin >>n) {
        if (n>0) ++pos;
        else if (n<0) ++neg;
        ++tot;
    }
    cout <<"Pos: "<<pos<<endl;
    cout <<"Neg: "<<neg<<endl;
    cout <<"Tot: "<<tot<<endl;
}

Suposo que s'ha entès el procediment. 

Al proper article, parlaré de llibreries (que contenen funcions ja definides), i també de com crear les nostres pròpies funcions

dimarts, 6 de juny del 2017

Aprendre a programar 5 - tractament seqüèncial

Una cosa molt important és no ocupar més memòria de la necessària quan programem. Quan volem calcular una cosa sobre una sèrie de dades, no hem de desar-les totes si no és necessari. Això és el tractament seqüencial, anar tractant les dades a mida que entren. Per exemple, suposem que volem calcular el màxim d'una sèrie de números que ens entraran. Una opció seria utilitzar un vector per desar-los allà, i més tard buscar quin és l'element més gran. Però això implicaria desar n elements, i recórrer tots aquests el doble del necessari. En canvi, un tractament seqüencial seria una cosa així:

int maxim;
cin >>maxim;
int n;
while (cin >>n) {
    if (n>maxim) maxim=n;
}
cout <<maxim<<endl;

Només hem necessitat dues variables, una pel màxim provisional, i una altra pel número que llegim en cada moment. Bastant menys que si ho llegíssim prèviament

Un altre exemple. Imaginem que volem un programa que llegeixi una posició n, i una sèrie de números, i digui quin hi ha a la posició n (molt similar al problema P39225). El que podríem fer és:

int main () {
    int n;
    cin >>n;
    int num;
    for (int i=0;i<n;++i) {
        cin >>num;
    }
    cout <<num<<endl;
}

Algú podria pensar "Ei, però no estàs llegint tots els números". Exacte, això és molt important. La gràcia del tractament seqüencial és que, si ja podem saber el resultat amb les dades que tenim, no cal seguir. No cal llegir tots els números que entren, si ja sabem quin està a la posició n. 

Un altre exemple seria el problema P36430. Aquest ens explica que Fermat (el troll matemàtic *) va demostrar que x^n+y^n=z^n no tenia solució natural per n>2 (excepte amb x=y=z=0). No obstant, pel cas 2 hi ha diverses solucions, i el problema ens demana buscar-ne una. 
Concretament ens dóna 4 números, a, b, c i d, i ens demana trobar la solució mínima tal que x estigui entre a i b, i y entre c i d. 

int pot2(int a) {
    return a*a;
}

void fermat(int a, int b, int c, int d) {
    for (int i=a;i<=b;++i) {
        for (int j=c;j<=d;++j) {
            int aux1=pot2(i)+pot2(j);
            int aux2=sqrt(aux1);
            if (aux2*aux2==aux1) {
                cout <<i<<"^2 + "<<j<<"^2 = "<<aux2<<"^2"<<endl;
                return;
            }
        }
    }
    cout <<"Sense solucio!"<<endl;
}

A aquest problema li podríem dir "prova i error". El que fa és anar provant, fins que troba el resultat. En el moment que el troba, fa el return, de manera que no segueix provant

* Fermat és el troll matemàtic per excel·lència. L'Últim Teorema de Fermat no es diu així perquè fos l'últim que va fer, ja que això no se sap. Es diu així perquè va ser l'últim que es va publicar, ja que, de fet, el van publicar amb ell ja mort. Va aparèixer escrit, en el marge d'un llibre seu, juntament amb la frase "He descobert una demostració veritablement meravellosa d'aquesta proposició, però aquest marge és massa estret perquè hi càpiga". Fermat va morir al 1665, i fins al 1995 no es va demostrar que la (fins a aquell moment) Conjectura de Fermat era correcta. 330 anys per demostrar el teorema



Per altra banda, jo no els trobo gaire gràcia a aquests problemes. L'únic que cal tenir en compte és que, si trobes la solució, acabar. De fet, suposo que veient quants enviaments tinc en aquesta llista, es pot notar com d'interessants em semblen


Així que podem passar ràpid al següent article, on parlo d'emmagatzematge massiu de dades

Aprendre a programar aux - punters i referències

Aquest és un d'aquells articles que, realment, a PRO1 gairebé no tenen utilitat. L'únic que s'utilitza són les referències, i molt poc. No obstant, mai està de més saber aquestes coses, així que les poso totes en un article a part, i així qui pugui se les llegeix. Jo recomanaria llegir, si més no, la part de referències

Punters:

Fins ara hem vist variables que emmagatzemen valors. Tenim enters, caràcters, nombres reals... Fins i tot tenim strings, tot i que no és un tipus de variable, per emmagatzemar cadenes de caràcters.

Un punter no emmagatzema cap valor. El que emmagatzema és l'adreça d'una altra variable. És a dir, apunta a una altra variable. Per declarar-los, només cal posar el tipus al que apuntarem seguit d'un *. Seria una cosa així

int* p; //p és un punter

Aleshores, si volem donar-li valor, el que volem és donar-li una adreça. Per accedir a l'adreça d'una variable, utilitzem l'operador &, davant de la variable. És a dir, si féssim

int* p=&n;

p apuntarà a la variable n. Sempre que utilitzem l'operador & amb una variable de tipus T, ens retornarà un T*. Això ens permet fer

int **p2=&p;

I p2 apunta a p.

Com mirem la variable a la que apunta un punter?

Si volem mirar la variable a la que apunta un punter, es fa amb l'operador de desreferència. Aquest és l'asterisc. Així, si fem

...
int* p;
...
int num=*p;

A num posarem el número que conté la variable apuntada per p. 
Si tinguéssim una struct com la següent

struct S {
    int a, b;
};

I un punter a una struct d'aquestes, podríem fer

int a=p->a;
int b=p->b;

I això equivaldria a fer

int a=(*p).a;
int b=(*p).b;

En C els punters estaven fins i tot a la sopa. Com que no s'havien inventat les referències, es passaven punters a les variables. De fet, això es segueix conservant parcialment, si utilitzes la funció scanf de la llibreria cstdlib, has de passar-li un punter a la variable. És a dir, si volguéssim llegir un enter, podríem fer

int n;
scanf("d",&n);

Això equivaldria a fer

int n;
cin >>n;

No obstant, ara, a C++, els punters perden moltes de les utilitats que tenien abans. Segueixen sent molt útils, però per altres coses

Una cosa en la que potser algú s'ha fixat és en que, mentre que cada tipus de variable ocupa un espai diferent en memòria, un punter emmagatzema adreces. Això fa que un int* tingui la mateixa mida que un char*, o un long*. Aleshores, podríem pensar en utilitzar un punter sense saber, a priori, a què apunta. Total, la mida que ocupen tots els punters és la mateixa...

Void*

Espera, espera, no m'he equivocat? Allà posa void*, és a dir, punter a void. Però no podem crear una variable void, no? No té cap sentit, void vol dir buit. O potser sí que té sentit?

Al cap i a la fi, una funció void és aquella que no retorna res. Per tant, un void* pot ser un punter que no apunta a res en concret. Això ens permet tenir funcions que rebin i/o retornin punters sense indicar a què apunten. Un exemple seria la funció scanf, que rep un punter i un string (un string de C; és a dir, un char[] o char*) que li indica a què apunta aquell punter. La funció fa la conversió, llegeix i emmagatzema allà. 

Un altre exemple seria la funció malloc. Aquesta funció serveix per reservar memòria en el heap, que és una regió de la memòria molt útil (es reserva dinàmicament, no pertany a cap funció en concret...). Si volguéssim reservar espai per 100 enters sense utilitzar cap classe (és a dir, no podem utilitzar vectors), podríem utilitzar-la. Faríem

int* arr=malloc(100*sizeof(int));

I arr apuntaria al primer de 100 enters que tenim reservats. De fet, és bastant similar al que fa internament la classe vector. Com podem veure, la funció malloc sempre rep el mateix, que és un enter que li indica quant espai volem reservar (recordem que la funció sizeof retorna la mida en bytes del que li passem). Per tant, no sap en cap moment què desarem allà, i ens retorna un void* apuntant a l'inici. Nosaltres, com que l'emmagatzemem a un int*, ja estem fent una conversió implícita
Com a parèntesi important, si fem un malloc, sempre hem de recordar que hem d'alliberar aquest espai. Es fa amb la funció free, que rep el punter que apunta a l'espai a alliberar. És a dir, si volem alliberar l'espai reservat per arr, faríem
free(arr);

Referències

En C++ apareixen les referències. El concepte és molt senzill. Enlloc d'apuntar a la variable, per què no fer que siguin exactament el mateix. És a dir, quan tu declares una variable n, el que fas és reservar una regió de memòria per n. Quan fas un punter, reserves espai per ell, i fas que contingui l'adreça de la variable on apuntes. La idea de la referència és que les dues variables comparteixin la regió de memòria, de manera que quan es modifica un, es modifica l'altre. 

Com es declara?

Fàcil. Per declarar una referència a T, fas T&. Per exemple, si volguéssim declarar una referència a un enter, faríem

int& enter=n;

Això faria que enter i n fossin la mateixa variable, quan canvies el valor d'una canvia la de l'altra. 

Per a què serveix això?

Per moltes coses. Imaginem que tenim una matriu mat, i volem modificar-la de manera que el primer element sigui igual al més petit de la matriu, i l'últim el més gran. Podríem fer això:

void arreglar(Matriu& mat) {
    int n=mat.size();
    int m=mat[0].size();
    for (int i=0;i<n;++i) {
        for (int j=0;j<m;++j) {
            if (primer>mat[i][j]) mat[0][0]=mat[i][j];
            else if (ultim<mat[i][j]) mat[n-1][m-1]=mat[i][j];
        }
    }
}

No obstant, podríem fer això:

void arreglar(Matriu& mat) {
    int n=mat.size();
    int m=mat[0].size();
    int& primer=mat[0][0];
    int& ultim=mat[n-1][m-1];
    for (int i=0;i<n;++i) {
        for (int j=0;j<m;++j) {
            if (primer>mat[i][j]) primer=mat[i][j];
            else if (ultim<mat[i][j]) ultim=mat[i][j];
        }
    }
}

Queda una mica més clar, no? I quan arribes més endavant, i tens un vector de llistes on, a dins de cada una, tens un map i a dins un altre vector, i necessites tenir accés a un element en concret d'aquests, doncs és útil. Sobretot perquè l'accés a un map no té temps constant

Funcions que modifiquen la variable

Aquesta és una altra utilitat de les referències. Quan passes una variable a una funció, es passa per còpia. No obstant, si passes una referència, podràs modificar la variable. Això té diverses utilitats:
1: Que la funció modifiqui una variable. Un exemple seria una funció per llegir una variable, l'hauríem de modificar. Un altre seria el següent:
void swap (int& a, int& b) {
    int aux=a;
    a=b;
    b=aux;
}
Això intercanvia els valors de la variable a i la variable b

2: Retornar més d'una variable. Per exemple, imaginem que volem fer una funció que calculi l'element n de la successió de fibonacci. Però hem pensat que podem retornar també l'element n-1, així amb una sola crida podrem fer-ho. Podem retornar una parella, o podem passar-li una variable per referència, i que allà ens deixi l'element n-1. És una solució cutre, però què hi farem

3: Estalviar-nos cost. Si volem buscar el màxim d'un vector de 1000 elements, no el passarem per còpia, ja que és molt llarg, i no ens cal. Podem passar-lo per referència constant. De la mateixa manera, imaginem una funció per llegir un vector. Podem fer

vector<int> v=llegir_vector();

Però això el que farà és llegir el vector en un vector auxiliar, i copiar-lo a v. Però clar, si el vector té 1.000.000 d'elements, doncs estarem copiant 1.000.000 d'elements. El passem per referència, i ja ens estalviem tot això. Fem una funció

void llegir_vector(vector<int>& v) {
    int n;
    cin >>n;
    v=vector<int>(n);
    for (int i=0;i<n;++i) cin >>v[i];
}

La cridem fent

vector<int> v;
llegir_vector(v);

I ja tenim el vector, sense fer còpies inútils

Aprendre a programar aux - Conversió implícita i explícita

Aquest és un d'aquells temes que mai se sap on tractar. El que he pensat és que el millor és fer un article independent, i anar posant un enllaç a tot arreu on em sembli necessari. Per tant, no cal esperar entendre-ho tot en el moment que ho llegeixis per primer cop, més aviat, això pot servir per anar-ho consultant cada cop que faci falta

Què és una conversió?

Fàcil. Sabem que una variable, a C++, té una certa mida. Què passa si tenim un codi com el següent?

signed char c=-1;
unsigned int i=3;
cout <<i+c<<endl;

Com es sumen un char (8 bits) amb signe, amb un enter (32 bits) sense signe? Això és la conversió

Nota 1: Si posem char a seques, depenent del sistema, tindrà signe o no. Si volem assegurar-nos de que en tingui, el millor és remarcar-ho. És l'únic tipus on passa això, a la resta, si no posem res, són amb signe
Nota 2: Si posem "unsigned" a seques, s'entén que és "unsigned int". Passa el mateix amb "short", "long", i "long long", s'entén que són enters
Això no és necessari saber-ho, però jo ho poso:
-1, utilitzant 8 bits, és 11111111, és a dir, 0xFF
-1, utilitzant 32 bits, és 0xFFFFFFFF
1, utilitzant 8 bits, és 0x01
1, utilitzant 32 bits, és 0x00000001
De fet, el bit que marca el signe és el primer. El que es fa és convertir la resta del número com sempre (és a dir, 2^0*b0+2^1*b1+2^2*b2...+2^(n-2)*b(n-2)-2^(n-1)*b(n-1). És a dir, el bit de més pes és negatiu. 
D'això podem deduir que, si volem convertir un número amb signe, el que hem de fer és posar a l'esquerra el bit de més pes. A això se li diu extensió de signe


Conversió implícita:

És aquella que es produeix sense que nosaltres diguem que s'ha de fer. Per exemple, si féssim

int n;
...
double d=n;

Això seria una conversió implícita. Converteix n a double per poder-ho desar a d. Un exemple més típic. Imaginem que volem implementar el xifrat Cèsar. Aquest el que fa és agafar dues tires amb l'abecedari, i posar-les una a sobre de l'altra, de manera que la de sota està desplaçada n vegades. Per xifrar una paraula, aniríem lletra per lletra, i mirant a quina lletra equival. Per exemple, amb n=1, la a equival a la b, la b a la c... Fins a arribar a la z, que equival a la a. Per tant, el codi d'una funció que fes això, seria:

bool es_lletra(char c) {
    return c>='a' and c<='z';
}

void cesar(int n) {
    char c;
    while (cin >>c) {
        if (es_lletra(c)) {
            char aux=c+n;
            if (not es_lletra(aux)) {
                aux='a'+(aux-'z');
                cout <<aux;
            }
            else cout <<aux;
        }
        else cout <<c;
    }
    cout <<endl;
}

Aquí fem diverses conversions implícites. Quan fem c+n, el que fem és convertir el caràcter c a int, per sumar-li n, i tornar-lo a convertir a char per desar-ho a n. Igual quan fem aux='a'+(aux-'z'), aquí el que es fa és convertir-se tot a int per fer el càlcul, i finalment es transforma en char. De fet, si proves a fer el cout directament, et mostrarà un número

Això té diversos problemes. Imaginem que volem fer la divisió amb decimals de dos enters. Si fem
double d=n/i;
n és enter, i també. Farà la divisió entera (és a dir, truncant decimals) per, acte seguit, convertir-ho a d. Perdrem els decimals. Hem d'anar amb compte i, sempre que no tinguem clar com es fa la conversió, o necessitem assegurar-nos de que es fa d'una manera en concret, utilitzar la conversió explícita

Aleshores, els passos de conversió són (trets de C Con Clase):
1: Els enters petits es transformen en int (és a dir, els char i els short) o unsigned int. 
2: Si un operand és de coma flotant, l'altre es transforma. El tipus serà el més gran. És a dir, si hi ha un long double, l'altre s'hi converteix. Si no, però hi ha un double, ídem. I si un no és cap dels anteriors, però sí un float, s'hi converteixen
3: Si són enters els dos, el petit es converteix en el tipus gran. És a dir, si tenim un long long, i una del tipus que sigui, es converteix en long long. Igual amb long

És a dir, en essència, si un operand és un número en coma flotant, l'altre es converteix a coma flotant per poder operar. Un cop tots dos són del mateix format (enter o coma flotant), es converteixen en el més gran

El problema aquí és què passa quan vols operar amb una variable unsigned, i una signed. Aquí salta un warning. El millor que podem fer és conversió explícita

Conversió explícita (o casting):

Aquesta és clara. És la que utilitzem quan volem remarcar una conversió. Un exemple seria:

cout <<"La "<<c<<" es la "<<int(c-'a'+1)<<" lletra de l'abecedari"<<endl;

Aquí ens volem assegurar de que mostri un número, interpretat com a número. De la mateixa manera, si volem la divisió decimal de dos enters, podem fer

double d=double(n)/i;

Només cal convertir n, perquè C++ té definida la divisió de double entre enter, que dóna un double. No obstant, podem convertir els dos per assegurar-nos. Ara, sempre cal fer-ho abans de la divisió, si ho convertim després no guanyarem res respecte a la implícita 

Com es fa la conversió explícita? C vs C++

Els programadors de C estareu acostumats a, quan voleu convertir, fer una cosa així:
(int) c;
Això és l'estil de C. No obstant, amb C++, malgrat es manté la possibilitat anterior, apareix una nova manera. Té forma de funció, i és com el següent:
int(c);
Les dues serveixen, no obstant, jo crec que amb la de C++ queda més clar què estem convertint. 

La única excepció és quan vols convertir punters. En aquest cas només es pot utilitzar l'estil C. Per exemple, per convertir un punter a long a un punter a float, faríem:

(float *) l;

Aquestes conversions són més típiques del que semblaria. Com a curiositat, hi ha un algorisme, que és l'algorisme Fast Inverse Square Root. Aquest el que fa és calcuar 1/sqrt(n) d'una manera molt més ràpida que la típica (calcular l'arrel i fer 1/arrel). Ho fa utilitzant conversions de punters

float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;

x2 = number * 0.5F;
y  = number;
i  = * ( long * ) &y;                       // evil floating point bit level hacking
i  = 0x5f3759df - ( i >> 1 );               // what the fuck? 
y  = * ( float * ) &i;
y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
// y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

return y;
}
L'he posat tal qual, amb els comentaris originals i tot. No espero que l'entengui ningú que estigui cursant PRO1, però perquè us feu una idea, el que fa aquí no té res a veure, ni amb l'arrel, ni amb invertir un número. És màgia. Buscant a Google podeu trobar més informació sobre l'algorisme


Compte!

A vegades passa una cosa. Imaginem el cas següent:

unsigned int n=256
unsigned char c=n;

Sabem que un char ocupa com a mínim 1 byte. Sabem també que, en general, 1 byte són 8 bits. Per tant, un signed char té un valor entre -128 i 127. Un unsigned char, per la seva banda, té un valor entre 0 i 255. El 256, per tant, queda fora del seu rang. Què farà? Fàcil. 256 és 0x00000100 en 32 bits. Això es truncarà, i quedarà 0x00 (els 8 bits de menys pes). És com si féssim
unsigned u=-1;
-1 en 32 bits és 0xFFFFFFFF (de fet, són tot F, sigui la mida que sigui). Què passa? Que això s'ha de convertir a unsigned. El que fa és agafar-ho així tal qual, i quedaria 4.294.967.295. Això es pot utilitzar per posar el número més gran possible a una variable sense signe, tot i que és millor utilitzar la llibreria limits. 

#include <limits>
...
...
int maxim=numeric_limits<int>::max();

I maxim contindria el valor més gran possible