# Fundamentos de Matemática Discreta com Python 

## Matemática Discreta

- Área da Matemática que lida com objetos discretos, a saber, conjuntos, sequencias, listas, coleções ou quaisquer entidades *contáveis*. 
- Exemplo, $\mathbb{R}$ é incontável, ou não enumerável
- Vários exemplos de contáveis:
    - O conjunto das vogais da língua portuguesa;
    - O conjunto dos times de futebol brasileiros da série A em 2020;
    - O conjunto de nomes das estações do ano;
    - O conjunto das personagens do quarteto do filme *Os Pinguins de Madagascar* e;
    - O conjunto dos números pares positivos menores ou iguais a dez.

- Conjuntos denotados por *extensão*: quando listamos seus elementos

- $\{ a, e, i, o, u \}$
- $\{ \text{Atlético-PR}, \ldots, \text{Bahia}, \text{Botafogo}, \ldots, \text{Coritiba}, \ldots, \text{Fortaleza}, \ldots, \text{Internacional}, \ldots, \text{São Paulo}, \text{Sport}, \text{Vasco} \}$
- $\{ \text{Primavera}, \text{Verão}, \text{Outono}, \text{Inverno}\}$
- $\{ \text{Capitão}, \text{Kowalski}, \text{Recruta}, \text{Rico}\}$
- $\{ 2, 4, 6, 8,10\}$

- Denotados por *compreensão*: quando usamos uma propriedade que distingue seus elementos. 

- $\{ c \in \mathbb{A} \, ; \, c \text{ é vogal} \}$
- $\{ t \in \mathbb{T} \, ; \, t \text{ é da Série A} \}$
- $\{ x  \, ; \, x \text{ é uma estação do ano} \}$
- $\{ p  \, ; \, p \text{ é personagem do quarteto principal do filme Os Pinguins de Madagascar} \}$
- $\{ e  \, ; \, e \text{ é estação do ano} \}$
- $\{ n \in \mathbb{Z} \, | \, n = 2k \wedge 2 \leq n \leq 10 \wedge k \in \mathbb{Z} \}$

Por livre conveniência: 

- $\mathbb{A}$ é o conjunto de todas as letras de nosso alfabeto
- $\mathbb{T}$ é o conjunto de todos os times de futebol do Brasil.

## Estruturas de dados para objetos discretos

As principais que aprenderemos: 

- `list`: estrutura cujo conteúdo é modificável e o tamanho variável. Listas são caracterizadas por *mutabilidade* e *variabilidade*. Objetos `list` são definidos por um par de colchetes e vírgulas que separam seus elementos: `[., ., ... ,.]`.

- `tuple`: estrutura cujo conteúdo não é modificável e o tamanho fixado. Tuplas são caracterizadas por *imutabilidade* e *invariabilidade*. Objetos `tuple` são definidos por um par de colchetes e vírgulas que separam seus elementos: `(., ., ... ,.)`.

- `dict`: estruturas contendo uma coleção de pares do tipo *chave-valor*. Dicionários são caracterizados por *arrays associativos* (*tabelas hash*). Objetos `dict` são definidos por um par de chaves e agrupamentos do tipo  `'chave':valor` (*key:value*), separados por vírgula: `{'chave1':valor1, 'chave2':valor2, ... ,'chaven':valorn}`. As chaves (*keys*) podem ser do tipo `int`, `float`, `str`, ou `tuple` ao passo que os valores podem ser de tipos arbitrários.

- `set`: estruturas similares a `dict`, porém não possuem chaves e contêm objetos únicos. Conjuntos são caracterizadas por *unicidade* de elementos. Objetos `set` são definidos por um par de chaves e vírgulas que separam seus elementos: `{., ., ... ,.}`.

## Listas

Estruturas `list` formam uma coleção de objetos arbitrários e podem ser criadas de modo sequenciado com operadores de pertencimento ou por expressões geradoras, visto que são estruturas iteráveis.


```python
vogais = ['a','e','i','o','u'] # elementos são 'str'
vogais
```




    ['a', 'e', 'i', 'o', 'u']




```python
times = ['Bahia', 'Sport', 'Fortaleza', 'Flamengo']
times 
```




    ['Bahia', 'Sport', 'Fortaleza', 'Flamengo']




```python
pares10 = [2,4,6,8,10]
pares10
```




    [2, 4, 6, 8, 10]




```python
mix = ['Bahia',24,6.54,[1,2]] # vários objetos na lista
mix 
```




    ['Bahia', 24, 6.54, [1, 2]]



### Listas por geração

**Exemplo**: crie uma lista dos primeiros 100 inteiros não-negativos.


```python
os_100 = range(100) # range é uma função geradora
print(list(os_100)) # casting com 'list'
```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]


**Exemplo**: crie o conjunto $\{ x \in \mathbb{Z} \, ; \, -20 \leq x < 10 \}$


```python
print(list(range(-20,10))) # print é usado para imprimir column-wise 
```

    [-20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


**Exemplo**: crie o conjunto $\{ x \in \mathbb{Z} \, ; \, -20 \leq x \leq 10 \}$


```python
print(list(range(-20,11))) # para incluir 10, 11 deve ser o limite. Por quê?
```

    [-20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


## Adicionando e removendo elementos

Há vários métodos aplicáveis para adicionar e remover elementos em listas.

### Adição por apensamento

Adiciona elementos por concatenação no final da lista.


```python
times.append('Botafogo')
times
```




    ['Bahia', 'Sport', 'Fortaleza', 'Flamengo', 'Botafogo']




```python
times.append('Fluminense')
times
```




    ['Bahia', 'Sport', 'Fortaleza', 'Flamengo', 'Botafogo', 'Fluminense']



### Adição por extensão 

Para incluir elementos através de um objeto iterável, sequenciável, usamos `extend`.


```python
falta = ['Vasco', 'Atlético-MG']
times.extend(falta) # usa outra lista pra estender a lista
times 
```




    ['Bahia',
     'Sport',
     'Fortaleza',
     'Flamengo',
     'Botafogo',
     'Fluminense',
     'Vasco',
     'Atlético-MG']



#### Iteração e indexação

- *Iterar* sobre uma lista é "passear" por seus elementos

- Em Python, a indexação de listas vai de `0` a `n - 1`, onde `n` é o tamanho da lista. 

Por exemplo:

$\text{posição} : \{p=0, p=1, \ldots, p={n-1}\}$

$\text{elementos na lista} : [x_1, x_2, \ldots, x_{n}]$

- Mesma idéia aplicável a qualquer coleção, sequencia ou objeto iterável.

### Remoção por índice

Suponha que tivéssemos criado a lista:


```python
pares = [0,2,5,6] # 5 não é par
pares
```




    [0, 2, 5, 6]



Como 5 não é par, não deveria estar na lista. Para excluírmos um elemento em uma posição específica, usamos `pop` passando o *índice* onde o elemento está. 


```python
pares.pop(2) # o ímpar 5 está na posição 2 e NÃO 3! 
pares

```




    [0, 2, 6]



### Adição por índice

Nesta lista, podemos pensar em incluir 4 entre 2 e 6. Para isto, usamos `insert(posicao,valor)`, para `valor` na `posicao` desejada.


```python
pares.insert(2,4) # 4 é inserido na posição de 6, que é deslocado
pares
```




    [0, 2, 4, 6]



### Apagar conteúdo da lista

Podemos apagar o conteúdo inteiro da lista com `clear`.


```python
times.clear()
times # lista está vazia 
```




    []



Podemos contar o número de elementos da lista com `len`.


```python
len(times) # verifica que a lista está vazia

```




    0




```python
type([]) # a lista é vazia, mas continua sendo lista 
```




    list



### Outros métodos de lista

Conte repetições de elementos na lista com `count`.


```python
numeros = [1,1,2,3,1,2,4,5,6,3,4,4,5,5]
print( numeros.count(1), numeros.count(3), numeros.count(7) )
```

    3 2 0


Localize a posição de um elemento com `index`.


```python
numeros.index(5) # retorna a posição da primeira aparição
```




    7



Remova a primeira aparição do elemento com `remove`.


```python
numeros.remove(1) # perde apenas o primeiro
numeros
```




    [1, 2, 3, 1, 2, 4, 5, 6, 3, 4, 4, 5, 5]



Faça uma reflexão ("flip") *in-place* (sem criar nova lista) da lista com `reverse`.


```python
numeros.reverse() 
numeros 
```




    [5, 5, 4, 4, 3, 6, 5, 4, 2, 1, 3, 2, 1]



Ordene a lista de maneira *in-place* (sem criar nova lista) com `sort`.


```python
numeros.sort()
numeros
```




    [1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6]



## Concatenação de listas

Listas são concatenadas ("somadas") com `+`. Caso já possua listas definidas, use `extend`.


```python
['Flamengo', 'Botafogo'] + ['Fluminense']  
```




    ['Flamengo', 'Botafogo', 'Fluminense']




```python
['Flamengo', 'Botafogo'] + 'Fluminense' # erro: 'Fluminense' não é list
```


```python
times_nordeste = ['Fortaleza','Sport']
times_sul = ['Coritiba','Atlético-PR']
times_nordeste + times_sul 
```




    ['Fortaleza', 'Sport', 'Coritiba', 'Atlético-PR']




```python
times_nordeste.extend(times_sul) # mesma coisa
times_nordeste 
```




    ['Fortaleza', 'Sport', 'Coritiba', 'Atlético-PR']



## Fatiamento de listas 

O fatiamento ("slicing") permite que selecionemos partes da lista através do modelo `start:stop`, em que `start` é um índice incluído na iteração, e `stop` não. 


```python
letras = ['a','b','c','d','e','f','g']
letras[0:2]
```




    ['a', 'b']




```python
letras[1:4]
```




    ['b', 'c', 'd']




```python
letras[5:6]
```




    ['f']




```python
letras[0:7] # toda a lista
```




    ['a', 'b', 'c', 'd', 'e', 'f', 'g']



### Omissão de `start` e `stop`


```python
letras[:3] # até 3, exclusive
```




    ['a', 'b', 'c']




```python
letras[:5] # até 5, exclusive
```




    ['a', 'b', 'c', 'd', 'e']




```python
letras[4:] # de 4 em diante
```




    ['e', 'f', 'g']




```python
letras[6:] # de 6 em diante 
```




    ['g']



### Modo reverso


```python
letras[-1] # último índice 
```




    'g'




```python
letras[-2:-1] # do penúltimo ao último, exclusive
```




    ['f']




```python
letras[-3:-1] 
```




    ['e', 'f']




```python
letras[-4:-2]
```




    ['d', 'e']




```python
letras[-7:-1] # toda a lista
```




    ['a', 'b', 'c', 'd', 'e', 'f']




```python
letras[-5:] 
```




    ['c', 'd', 'e', 'f', 'g']




```python
letras[:-3] 
```




    ['a', 'b', 'c', 'd']



## Elementos alternados com `step`

Podemos usar um dois pontos duplo (`::`) para dar um "passo" de alternância.


```python
letras[::2] # salta 2-1 intermediários 
```




    ['a', 'c', 'e', 'g']




```python
letras[::3] # salta 3-1 intermediários 
```




    ['a', 'd', 'g']




```python
letras[::7] # salto de igual tamanho 
```




    ['a']




```python
letras[::8] # salto além do tamanho
```




    ['a']



## Mutabilidade de listas

Podemos alterar o conteúdo de elementos diretamente por indexação.


```python
from sympy.abc import x,y

ops = [x+y,x-y,x*y,x/y]
ops2 = ops.copy() # cópia de ops
ops
```




    [x + y, x - y, x*y, x/y]




```python
ops[0] = x-y
ops
```




    [x - y, x - y, x*y, x/y]




```python
ops[2] = x/y
ops
```




    [x - y, x - y, x/y, x/y]




```python
ops[1], ops[3] = x + y, x*y # mutação por desempacotamento
ops
```




    [x - y, x + y, x/y, x*y]




```python
ops[1:3] = [False, False, True] # mutação por fatiamento
ops
```




    [x - y, False, False, True, x*y]




```python
ops = ops2 # recuperando ops 
ops
```




    [x + y, x - y, x*y, x/y]




```python
ops2 is ops
```




    True




```python
ops3 = [] # lista vazia
ops3
```




    []




```python
ops2 = ops + ops3 # concatenação cria uma lista nova
ops2
```




    [x + y, x - y, x*y, x/y]




```python
ops2 is ops # agora, ops2 não é ops 
```




    False




```python
print(id(ops), id(ops2)) # imprime local na memória de ambas
```

    140614135474440 140614130646920



```python
ops2 == ops # todos os elementos são iguais
```




    True



O teste de identidade é `False`, mas o teste de igualdade é `True`.

**Exemplo:** Escreva uma função que calcule a área, perímetro, comprimento da diagonal, raio, perímetro e área do círculo inscrito, e armazene os resultados em uma lista. 


```python
# usaremos matemática simbólica
from sympy import symbols
from math import pi

# símbolos
B, H = symbols('B H',positive=True)

def propriedades_retangulo(B,H):
    '''
        A função assume que a base B 
        é maior do que a altura H. Senão, 
        as propriedades do círculo inscrito 
        não serão determinadas.        
    '''    
    d = (B**2 + H**2)**(1/2) # comprimento da diagonal
    r = H/2 # raio do círculo inscrito    
    return [B*H, 2*(B+H), d, r, 2*pi*r, pi*(r)**2]

# lista de objetos símbolos
propriedades_retangulo(B,H) 
```




    [B*H,
     2*B + 2*H,
     (B**2 + H**2)**0.5,
     H/2,
     3.14159265358979*H,
     0.785398163397448*H**2]




```python
# substituindo valores
B, H = 4.0, 2.5

propriedades_retangulo(B,H)
```




    [10.0, 13.0, 4.716990566028302, 1.25, 7.853981633974483, 4.908738521234052]



### Formatação de strings


O *template* a seguir usa a função `format` para substituição de valores indexados.

```python
templ = '{0} {1} ... {n}'.format(arg0,arg1,...,argn)
```

**Nota:** Para ajuda plena sobre formatação, consultar: 

```python
help('FORMATTING')
```


```python
# considere R: retângulo; C: círculo inscrito

res = propriedades_retangulo(B,H) # resultado

props = ['Área de R',
         'Perímetro de R',
         'Diagonal de R',
         'Raio de C',
         'Perímetro de C',
         'Área de C'
        ] # propriedades

# template
templ = '{0:s} = {1:.2f}\n\
{2:s} = {3:.3f}\n\
{4:s} = {5:.4f}\n\
{6:s} = {7:.5f}\n\
{8:s} = {9:.6f}\n\
{10:s} = {11:.7f}'.format(props[0],res[0],\
                          props[1],res[1],\
                          props[2],res[2],\
                          props[3],res[3],\
                          props[4],res[4],\
                          props[5],res[5])

# impressão formatada
print(templ)
```

    Área de R = 10.00
    Perímetro de R = 13.000
    Diagonal de R = 4.7170
    Raio de C = 1.25000
    Perímetro de C = 7.853982
    Área de C = 4.9087385


### Como interpretar o que fizemos? 

- `{0:s}` formata o primeiro argumento de `format`, o qual é `props[0]`, como `str` (`s`).
- `{1:.2f}` formata o segundo argumento de `format`, o qual é `res[0]`, como `float` (`f`) com duas casas decimais (`.2`).
- `{3:.3f}` formata o quarto argumento de `format`, o qual é `res[1]`, como `float` (`f`) com três casas decimais (`.3`).

A partir daí, percebe-se que um template `{X:.Yf}` diz para formatar o argumento `X` como `float` com `Y` casas decimais, ao passo que o template `{X:s}` diz para formatar o argumento `X` como `str`.

Além disso, temos:

- `\n`, que significa "newline", isto é, uma quebra da linha.
- `\`, que é um *caracter de escape* para continuidade da instrução na linha seguinte. No exemplo em tela, o *template* criado é do tipo *multi-line*. 

**Nota:** a contrabarra em `\n` também é um caracter de escape e não um caracter *literal*. Isto é, para imprimir uma contrabarra literalmente, é necessário fazer `\\`. Vejamos exemplos de literais a seguir.

#### Exemplos de impressão de caracteres literais


```python
print('\\') # imprime contrabarra literal
print('\\\\') # imprime duas contrabarras literais
print('\'') # imprime plica
print('\"') # imprime aspas
```

    \
    \\
    '
    "


#### f-strings

Temos uma maneira bastante interessante de criar templates usando f-strings, que foi introduzida a partir da versão Python 3.6. Com f-strings a substituição é imediata.


```python
print(f'{props[0]} = {res[0]}') # estilo f-string
```

    Área de R = 10.0


#### Estilos de formatação

Veja um comparativo de estilos:


```python
print('%s = %f ' % (props[0], res[0])) # Python 2
print('{} = {}'.format(props[0], res[0])) # Python 3
print('{0:s} = {1:.4f}'.format(props[0], res[0])) # Python 3 formatado
```

    Área de R = 10.000000 
    Área de R = 10.0
    Área de R = 10.0000


**Exemplo:** Considere o conjunto: V = $\{ c \in \mathbb{A} \, ; \, c \text{ é vogal} \}.$ Crie a concatenação de todos os elementos com f-string.


```python
V = ['a','e','i','o','u']
V 
```




    ['a', 'e', 'i', 'o', 'u']




```python
f'{V[0]}{V[1]}{V[2]}{V[3]}{V[4]}' # pouco Pythônico
```




    'aeiou'



Veremos à frente meios mais elegantes de fazer coisas similares.

## Controle de fluxo: laço `for`

Em Python, podemos realizar iterar por uma coleção ou iterador usando *laços*. Introduziremos aqui o laço `for`. Em Python, o bloco padrão para este laço é dado por: 

```python
for valor in sequencia:
    # faça algo com valor
```

Acima, `valor` é um iterador.


```python
for v in vogais: # itera sobre lista inteira
    print(v)
```

    a
    e
    i
    o
    u



```python
for v in vogais[0:3]: # itera parcialmente
    print(v + 'a')
```

    aa
    ea
    ia



```python
for v in vogais[-2:]: 
    print(f'{v*10}')
```

    oooooooooo
    uuuuuuuuuu


## Compreensão de lista

Usando `for`, a criação de listas torna-se bastante facilitada.

**Exemplo:** crie a lista dos primeiros 10 quadrados perfeitos.


```python
Q = [q*q for q in range(1,11)]
Q
```




    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]



A operação acima equivale a:


```python
Q2 = []
for q in range(1,11):
    Q2.append(q*q)
Q2        
```




    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]



**Exemplo:** crie a PA: $a_n = 3 + 6(n-1), \, 1 \leq n \leq 10$


```python
PA = [3 + 6*(n-1) for n in range(1,11) ]
PA
```




    [3, 9, 15, 21, 27, 33, 39, 45, 51, 57]



**Exemplo:** se $X = \{1,2,3\}$ e $Y=\{4,5,6\}$, crie a "soma" $X + Y$ elemento a elemento.


```python
X = [1,2,3]
Y = [4,5,6]

XsY = [ X[i] + Y[i] for i in range(len(X)) ]
XsY
```




    [5, 7, 9]



**Exemplo:** se $X = \{1,2,3\}$ e $Y=\{4,5,6\}$, cria o "produto" $X * Y$ elemento a elemento.


```python
XpY = [ X[i]*Y[i] for i in range(len(X)) ]
XpY

from sympy import lambdify
from sympy.abc import x

for i,v in enumerate(XpY)
    lambdify(x,'x**2')
```

## Tuplas

Tuplas são são sequencias imutáveis de tamanho fixo. Em Matemática, uma tupla é uma sequência ordenada de elementos. Em geral, o termo $n-$upla ("ênupla") é usado para se referir a uma tupla com $n$ elementos.


```python
par = 1,2; par
```




    (1, 2)




```python
type(par)
```




    tuple




```python
trio = (1,2,3); trio
```




    (1, 2, 3)




```python
quad = (1,2,3,4); quad
```




    (1, 2, 3, 4)




```python
nome = 'Nome'; tuple(nome) # casting
```




    ('N', 'o', 'm', 'e')



Tuplas são acessíveis por indexação.


```python
quad[2]
```




    3




```python
quad[1:4]
```




    (2, 3, 4)




```python
quad[3] = 5 # tuplas não são mutáveis
```

Se na tupla houver uma lista, a lista é modificável.


```python
super_trio = tuple([1,[2,3],4]) # casting
super_trio
```




    (1, [2, 3], 4)




```python
super_trio[1].extend([4,5]) 
super_trio
```




    (1, [2, 3, 4, 5], 4)



Tuplas também são concatenáveis com `+`.


```python
(2,3) + (4,3)
```




    (2, 3, 4, 3)




```python
('a',[1,2],(1,1)) # repetição
```




    ('a', [1, 2], (1, 1))



### Desempacotamento de tuplas


```python
a,b,c,d = (1,2,3,4)
```


```python
for i in [a,b,c,d]:
    print(i) # valor das variáveis
```

    1
    2
    3
    4



```python
a,b = (1,2)
a,b = b,a # troca de valores
a,b
```




    (2, 1)



### `enumerate`

Podemos controlar índice e valor ao iterar em uma sequencia.


```python
X = [1,2,3] # lista / sequencia

for i,x in enumerate(X): # (i,x) é uma tupla (índice,valor)
    print(f'{i} : {x}')
```

    0 : 1
    1 : 2
    2 : 3


**Exemplo:** Construa o produto cartesiano 

$$A \times B = \{(a,b) \in \mathbb{Z} \times \mathbb{Z} \, ; \, -4 \leq a \leq 4 \wedge 3 \leq b \leq 7\}$$


```python
AB = [(a,b) for a in range(-4,5) for b in range(3,8)]
print(AB)
```

    [(-4, 3), (-4, 4), (-4, 5), (-4, 6), (-4, 7), (-3, 3), (-3, 4), (-3, 5), (-3, 6), (-3, 7), (-2, 3), (-2, 4), (-2, 5), (-2, 6), (-2, 7), (-1, 3), (-1, 4), (-1, 5), (-1, 6), (-1, 7), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7)]


## Dicionários

Dicionários, ou especificamente, objetos `dict`, possuem extrema versatilidade e são muito poderosos. Criamos um `dict` por diversas formas. A mais simples é usar chaves e pares explícitos.


```python
d = {} # dict vazio
d
```




    {}




```python
type(d)
```




    dict



Os pares chave-valor incorporam quaisquer tipos de dados.


```python
d = {'par': [0,2,4,6,8], 'ímpar': [1,3,5,7,9], 'nome':'Meu dict', 'teste': True}
d
```




    {'par': [0, 2, 4, 6, 8],
     'ímpar': [1, 3, 5, 7, 9],
     'nome': 'Meu dict',
     'teste': True}



### Acesso a conteúdo

Para acessar o conteúdo de uma chave, indexamos pelo seu nome.


```python
d['par'] 
```




    [0, 2, 4, 6, 8]




```python
d['nome']
```




    'Meu dict'



**Exemplo:** construindo soma e multiplicação especial.


```python
# dict
op = {'X' :[1,2,3], 'delta' : 0.1}

# função
def sp(op):
    s = [x + op['delta'] for x in op['X']]
    p = [x * op['delta'] for x in op['X']]
    
    return (s,p) # retorna tupla

soma, prod = sp(op) # desempacota

for i,s in enumerate(soma):
    print(f'pos({i}) | Soma = {s} | Prod = {prod[i]}')
```

    pos(0) | Soma = 1.1 | Prod = 0.1
    pos(1) | Soma = 2.1 | Prod = 0.2
    pos(2) | Soma = 3.1 | Prod = 0.30000000000000004


### Inserção de conteúdo


```python
# apensa variáveis
op[1] = 3 
op['novo'] = (3,4,1) 
op
```




    {'X': [1, 2, 3], 'delta': 0.1, 1: 3, 'novo': (3, 4, 1)}



### Alteração de conteúdo


```python
op['novo'] = [2,1,4] # sobrescreve
op
```




    {'X': [1, 2, 3], 'delta': 0.1, 1: 3, 'novo': [2, 1, 4]}



### Deleção de conteúdo com `del` e `pop`


```python
del op[1] # deleta chave 
op
```




    {'X': [1, 2, 3], 'delta': 0.1, 'novo': [2, 1, 4]}




```python
novo = op.pop('novo') # retorna e simultaneamente deleta
novo
```




    [2, 1, 4]




```python
op
```




    {'X': [1, 2, 3], 'delta': 0.1}



### Listagem de chaves e valores

Usamos os métodos `keys()` e `values()` para listar chaves e valores.


```python
arit = {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/'} # dict
k = list(arit.keys())
print(k)
val = list(arit.values())
print(val)
for v in range(len(arit)):
    print(f'A operação \'{k[v]}\' de "arit" usa o símbolo \'{val[v]}\'.')
```

    ['soma', 'subtr', 'mult', 'div']
    ['+', '-', '*', '/']
    A operação 'soma' de "arit" usa o símbolo '+'.
    A operação 'subtr' de "arit" usa o símbolo '-'.
    A operação 'mult' de "arit" usa o símbolo '*'.
    A operação 'div' de "arit" usa o símbolo '/'.


### Combinando dicionários

Usamos `update` para combinar dicionários. Este método possui um resultado similar a `extend`, usado em listas.


```python
pot = {'pot': '**'}
arit.update(pot)
arit
```




    {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/', 'pot': '**'}



### Dicionários a partir de sequencias

Podemos criar dicionários a partir de sequencias existentes usando `zip`.


```python
arit = ['soma', 'subtr', 'mult', 'div', 'pot']
ops = ['+', '-', '*', '/', '**']

dict_novo = {}

for chave,valor in zip(arit,ops):
    dict_novo[chave] = valor
    
dict_novo
```




    {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/', 'pot': '**'}



Visto que um `dict` é composto de várias tuplas de 2, podemos criar um  de maneira ainda mais simples.


```python
dict_novo = dict(zip(arit,ops))
dict_novo
```




    {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/', 'pot': '**'}



### *Hashability*

Dissemos acima que os valores de um `dict` podem ser qualquer objeto Python. Porém, as chaves estão limitadas por uma propriedade chamada *hashability*. Um objeto *hashable* em geral é imutável. Para saber se um objeto pode ser usado como chave de um `dict`, use a função `hash`. Caso retorne erro, a possibilidade de *hashing* é descartada. 


```python
# todos aqui são imutáveis, portanto hashable' 
hash('s'), hash(2), hash(2.1), hash((1,2))
```




    (2979719690768129234, 2, 230584300921369602, 3713081631934410656)




```python
# não hashable 
hash([1,2]), hash((1,2),[3,4])
```

Para usar `list` como chave, podemos convertê-las em `tuple`.


```python
d = {}; d[tuple([1,2])] = 'hasheando lista em tupla'; d
```




    {(1, 2): 'hasheando lista em tupla'}



## Compreensão de dicionário

Podemos usar `for` para criar dicionários de maneira esperta do mesmo modo que as compreensões de lista com a distinção de incluir pares chaves/valor.


```python
{chave:valor for chave,valor in enumerate(arit)} # chave:valor
```




    {0: 'soma', 1: 'subtr', 2: 'mult', 3: 'div', 4: 'pot'}




```python
{valor:chave for chave,valor in enumerate(arit)} # valor:chave
```




    {'soma': 0, 'subtr': 1, 'mult': 2, 'div': 3, 'pot': 4}



15c953a7efc730f9918a00b03debe2df91f3a245
ipynb
Jupyter Notebook
_build/jupyter_execute/rise/03a-matematica-discreta-rise.ipynb
gcpeixoto/FMECD
9bca72574c6630d1594396fffef31cfb8d58dec2
CC0-1.0
_build/jupyter_execute/rise/03a-matematica-discreta-rise.ipynb
gcpeixoto/FMECD
9bca72574c6630d1594396fffef31cfb8d58dec2
CC0-1.0
_build/jupyter_execute/rise/03a-matematica-discreta-rise.ipynb
gcpeixoto/FMECD
9bca72574c6630d1594396fffef31cfb8d58dec2
CC0-1.0
Qwen/Qwen-72B
1. YES
2. YES