Proggers Bookclub

Clube do livro de programação.

abra um pr para editar essa pagina: github.com/guites/proggers.

C++ Primer

Array vs. Vector

Array (C-style):

Alem disso tudo, voce NAO pode usar o copy constructor dele. ex: arr1 = arr2; Como eles sao apenas pointers, o arr2 seria um pointer para o mesmo local de memoria do arr1, oq alem de ilegal, eh muito perigoso.

void badContainer()
{
    int arrSize{ 5 };
    int arr[arrSize] = { 10, 20, 30, 40, 50 };

    std::cout << "item 0: " << arr[0] << "\n"; // "Seguro" pq o index [0] existe

    std::cout << "item 5?: " << arr[5] << "\n"; // Provavelmente vai printar um numero aleatorio pq o index [5] nao existe entao o pointer aponta pra pro proximo index fora do array, ou seja, memoria suja/lixo. causa undefined behaviour.
    
    std::cout << "item 100000??: " << arr[100000] << "\n";  // index nao pertence ao array e provavelmente, por ser mt distante, nem a pagina de memoria do programa em si. causa segment fault (acessar memoria mt longe).
}

int main()
{
    badContainer();
    return 0;
}

std::vector:

#include <vector>
using namespace std;

void printContainer(const vector<int>& vec)
{
    // vec.size() retorna o tamanho correto do vetor
    // const type& evita copias desnecessarias, ou seja, parametro passado por referencia 
    
    // for simples
    for(int i = 0; i < vec.size(); i++)
    {
        cout << vec[i] << "\n";
    }

    // for in range 
    for(const auto& elem : vec)
    {
        cout << elem << "\n";
    }

}

int main()
{

    vector<int> vec1(5); // comeca com 5 elem e todos iniciados como 0
    vector<int> vec2 = {10, 20, 30}; // direct initialization (C++11 adiante);
   
    vec1 = vec2; // vec1 eh uma copia exata de vec2, redimensionado automaticamente

    printContainer(vec1);
    
    return 0;
}

Math Primer

Mathematical Induction

Usando o exemplo de uma fileira infinita de dominos em pe, voce quer provar que todos os dominos vao cair, sem ter que derrubar e checar um por um, oq seria impossivel por serem infinitos.

A inducao matematica diz que tu so precisa provar duas coisas pra garantir que todos os dominos caiam:

  1. Caso Base (Primeiro empurrao): Tu consegue provar que o primeiro domino cai?

  2. Passo Indutivo (Regra da reacao em cadeia): Tu consegue provar que, se um domino qualquer cair, ele obrigatoriamente derrubara o proximo?

Se tu conseguir provar essas duas coisas, a "magica" acontece:

1. Caso Base / Base Case

tanto na matematica, como na recursao, tudo precisa de um ponto de partida e/ou de parada.

2. Hipotese Indutiva / Inductive Hypothesis

pra provar que a regra funciona para todos os outros casos, voce diz:

"Vamos assumir que a regra funciona para um numero qualquer $k$, se essa suposicao for verdade, eu consigo provar que funciona pra $k + 1$ (o proximo numero)?"

tu nao ta assumindo que o resultado final eh verdadeiro, tu ta assumindo que a maquina/algoritmo funciona.

Analise de algoritmos

Agora que sabemos isso, podemos estudar o modo que o Weiss faz analise de algoritmos, que ocorre atraves do conceito de recursao, onde ele usa a inducao pra validar tanto a logica de um algoritmo quanto seu custo(tempo de execucao).

Regra do design e provando a veracidade do algoritmo

Segundo o manolo ai do livro, grande Weiss, entender inducao eh crucial pra entender recursao, e se tu tenta seguir o codigo linha por linha na tua cabeca, tu vai tontear negao.

Por isso que a inducao nos da a regra do design pra recursao:

"Assuma que todas as chamadas recursivas funcionam."

quando tu cria uma funcao recursiva, tu nao pode tentar simular oq o computador vai fazer, tu tem que usar a logica da inducao:

  1. Eu tratei o caso base? (programa sabe quando parar?)
  2. Eu fiz o progresso em direcao ao caso base(passo indutivo)? (reduzi o problema grande em um menor e assumi que a funcao vai resolver esse menor corretamente?)

ex fatorial: digamos que queremos calcular o fatorial de 5: $5!(5 \times 4 \times 3 \times 2 \times 1)$.

se tu confia na hipotese(que a funcao sabe resolver o menor problema), a solucao do problema maior se torna obvia.

Da inducao pra analise de tempo/complexidade

quando tu vai analisar um algoritmo recursivo, tu nao pode simplesmente contar as linhas de codigo, pq o codigo eh executado repetidamente, gerando relacoes de recorrencia e a inducao eh a ferramenta matematica que tu usa pra resolver essas equacoes de recorrencia e provar o custo de execucao do algoritmo ($O$), ou a famosa notacao *Big $O$.

ex do fibonacci:

long fib( int n ) 
{
    if( n <= 1 )
    {
        return 1; // Caso base
    } else {
        return fib( n - 1 ) + fib( n - 2 ); // Passo indutivo/recursivo
    }
}

e essa seria o flow de analise do algoritmo fibonacci:

Notacoes matematicas

pra nos nao parecermos uns lerdoes chutando "ah, esse eh rapido" ou "esse eh lento", a gente usa umas notacoes matematicas pra classificar o comportamento do algoritmo quando $N$ (tamanho do input) fica gigante.

  1. Big $O$ -> Teto (Limite superior/upper bound)
  2. Omega ($\Upomega$) -> Chao (Limite inferior/lower bound)