# Conjuntos e dicionários

Escolher estruturas para pertença, operações de conjuntos e contagens por chave.

Página: https://resumos.rgo.pt/cadeiras/ct-iadp/conjuntos-dicionarios/

Uma lista responde à pergunta “o que está na posição 2?”. Um conjunto responde “este valor está presente?”. Um dicionário responde “qual é o valor associado a esta chave?”. Escolhe a estrutura pela operação de que precisas.

## Conjuntos sem repetições

`set` guarda elementos distintos. `{1, 2, 2}` tem apenas 1 e 2. Não indexas um conjunto e não deves depender da ordem em que os seus valores aparecem. O conjunto vazio é `set()`; `{}` cria um dicionário vazio.

| Operação | Escrita | Significado |
| --- | --- | --- |
| União | `a | b` | Está em pelo menos um |
| Interseção | `a & b` | Está nos dois |
| Diferença | `a - b` | Está em `a` e não em `b` |
| Diferença simétrica | `a ^ b` | Está em exatamente um |
| Inclusão | `a <= b` | Todos os elementos de `a` estão em `b` |

Se `a = {"Porto", "Braga"}` e `b = {"Porto", "Aveiro"}`, a interseção é `{"Porto"}`. A diferença `a - b` é `{"Braga"}`. A direção da diferença faz parte do resultado.

`add(x)` acrescenta um elemento. `discard(x)` retira-o se existir. `remove(x)` exige que exista e lança `KeyError` caso contrário. Usa `sorted(conjunto)` para obter uma lista numa ordem reprodutível.

## Chaves de um dicionário

```
precos = {"pão": 1.5, "leite": 1.2}
print(precos["pão"])
```

A chave `"pão"` identifica um valor, 1.5. Uma chave repetida recebe o último valor atribuído. `"pão" in precos` testa as chaves, não os preços. `precos["arroz"]` lança `KeyError`; `precos.get("arroz", 0)` devolve zero quando não encontra a chave.

O zero por defeito só é adequado quando significa realmente “não havia unidades contadas” ou outra convenção declarada. Não uses zero para esconder um preço desconhecido.

As chaves e os elementos dos conjuntos têm de ser **hashable**, ou seja, suportar uma identidade de dispersão estável usada na procura. Inteiros, strings e tuplos de valores hashable servem. Listas e dicionários não servem. Um tuplo que contém uma lista também não serve como chave.

## Contar ocorrências

Vamos contar vendas por produto. Para cada produto, lemos a contagem anterior, usando zero quando aparece pela primeira vez, e somamos um.

```python
vendas = ["pão", "leite", "pão", "arroz", "leite", "pão"]
contagens = {}
for produto in vendas:
    contagens[produto] = contagens.get(produto, 0) + 1
for produto, quantidade in contagens.items():
    print(produto, quantidade)
print(sorted(set(vendas)))
```

As contagens são pão 3, leite 2 e arroz 1. O conjunto conserva apenas os nomes distintos. Os dicionários conservam a ordem de inserção das chaves em Python atual, mas a contagem não depende dessa ordem.

`keys()` percorre chaves, `values()` percorre valores e `items()` percorre pares chave-valor. Em `for produto, quantidade in contagens.items()`, o par de cada iteração é desempacotado nos dois nomes.

## Alterar um dicionário e interpretar o retorno

| Operação | Efeito e retorno |
| --- | --- |
| `d.pop(chave)` | Remove e devolve o valor; `KeyError` se faltar |
| `d.pop(chave, valor_padrao)` | Devolve o padrão se a chave faltar |
| `del d[chave]` | Remove sem devolver o valor; `KeyError` se faltar |
| `d.popitem()` | Remove e devolve o último par inserido; `KeyError` se vazio |
| `d.setdefault(chave, valor)` | Devolve o valor existente ou insere e devolve o padrão |
| `d.update(outro)` | Acrescenta ou substitui pares; devolve `None` |
| `d.clear()` | Esvazia o dicionário; devolve `None` |
| `d.copy()` | Devolve uma cópia superficial |
| `dict.fromkeys(chaves, valor)` | Cria chaves associadas ao mesmo objeto de valor |

`get` lê sem inserir; `setdefault` pode inserir. `update` substitui valores de chaves existentes e acrescenta chaves novas. Não atribuas o retorno de `update` ao dicionário, porque esse retorno é `None`.

```
stock = {"A": 2}
chaves = stock.keys()
print(stock.setdefault("A", 9))  # 2, sem substituir
stock.setdefault("B", 4)
stock.update({"A": 3})
print(list(chaves))               # ['A', 'B']
print(stock.popitem())            # ('B', 4)
print(list(chaves))               # ['A']
```

Em Python atual, `keys`, `items` e `values` devolvem **vistas**, que refletem alterações ao dicionário, e não listas independentes. Usa `list(...)` para guardar uma sequência do estado naquele momento. Evita acrescentar ou remover chaves enquanto percorres a vista.

`dict.fromkeys(["A", "B"], [])` partilha a mesma lista entre os dois valores. Para listas independentes, usa `{chave: [] for chave in ["A", "B"]}`. Uma cópia superficial de um dicionário também continua a partilhar objetos mutáveis guardados como valores.

## Escolher a estrutura

| Precisas de… | Estrutura |
| --- | --- |
| Conservar a ordem e as repetições das vendas | Lista |
| Guardar um par fixo de coordenadas | Tuplo |
| Saber que produtos apareceram pelo menos uma vez | Conjunto |
| Associar cada produto ao seu preço | Dicionário |

Converter uma lista para conjunto perde repetições e a ordem original. Não é uma operação neutra. Se cada ocorrência representar uma venda, essa conversão já não preserva o número de vendas.

## Exercícios

Escolher a direção da diferença

a = 4 e b = 5. Quantos elementos tem a - b?

Primeira pista

Retira de a os valores que também estão em b.

Mais uma pista

Restam 1 e 2.

Ver solução

Há dois elementos. b - a seria o conjunto com 5 e teria um elemento.

#### Erros frequentes

Usar a união ou ignorar que a direção da diferença muda o resultado.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/conjuntos-dicionarios/#conjuntos-sem-repeti%C3%A7%C3%B5es)

Contar sem perder ocorrências

Escreve contagens(valores), que devolve um dicionário de frequências. O resultado para a, b, a deve ser a: 2 e b: 1. Para lista vazia, devolve dicionário vazio.

```python
def contagens(valores):
    return {}

assert contagens([]) == {}
assert contagens(["a", "b", "a"]) == {"a": 2, "b": 1}
assert contagens([3, 3, 3]) == {3: 3}
print("Casos verificados")
```

Primeira pista

Cada valor é uma chave.

Mais uma pista

Usa resultado.get(valor, 0) + 1 em cada iteração.

Ver solução

Cria um dicionário vazio, percorre os valores e atualiza a contagem da chave. Devolve o dicionário no fim do ciclo.

Confere a tua resposta:

*   Contei cada ocorrência sem converter a entrada em conjunto.
*   Tratei a primeira ocorrência com contagem inicial zero.
*   Passei os casos vazio e repetido.

#### Erros frequentes

Usar um conjunto perde repetições; colocar return dentro do ciclo interrompe na primeira ocorrência.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/conjuntos-dicionarios/#contar-ocorr%C3%AAncias)

Ler ou inserir sem substituir

`stock = {"A": 2}; resultado = stock.setdefault("A", 9)`. Quanto vale resultado?

Primeira pista

A chave A já existe.

Mais uma pista

O valor por defeito só é inserido quando a chave está ausente.

Ver solução

Devolve 2 e stock conserva a chave A associada a 2. A chamada não substitui um valor existente.

#### Erros frequentes

Confundir setdefault com uma atribuição explícita ou com update.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/conjuntos-dicionarios/#alterar-um-dicion%C3%A1rio-e-interpretar-o-retorno)
