Dicionários e conjuntos
Associações por chave, frequências, operações de conjuntos e escolha de estruturas.
Com notas = {"Ana": 15, "Bruno": 12}, consultamos a nota de Ana pelo nome sem saber uma posição; um dicionário associa chaves a valores, enquanto um conjunto guarda apenas os nomes sem contar repetições quando só queremos saber quem participou.
Dicionários, chaves e valores
notas = {"Ana": 15, "Bruno": 12} cria duas associações. notas["Ana"] vale 15. Atribuir novamente à mesma chave substitui o valor, não cria outra entrada. del notas["Bruno"] retira a associação.
Uma chave tem de ser hashable: tem um código numérico, chamado hash, que não muda enquanto for usada como chave.
- Objetos iguais têm de ter hashes iguais; objetos diferentes podem partilhar um hash.
- O dicionário usa esse código para encontrar candidatos e a igualdade para distinguir chaves.
Strings, inteiros e tuplos de elementos hashable servem. Listas, dicionários e conjuntos mutáveis não servem como chaves. Os valores não têm essa restrição.
chave in d verifica chaves, não valores. d[chave] lança KeyError se a chave não existir. d.get(chave, padrao) devolve o padrão sem criar a entrada. d.keys(), d.values() e d.items() permitem percorrer chaves, valores e pares. Os dicionários conservam ordem de inserção, mas não ordenam as chaves.
Contar frequências
Queremos contar votos, incluindo nomes repetidos: antes de ver um nome pela primeira vez, a frequência é zero, e cada ocorrência aumenta-a em um.
def frequencias(nomes):
contagem = {}
for nome in nomes:
contagem[nome] = contagem.get(nome, 0) + 1
return contagem
print(frequencias(["Ana", "Bruno", "Ana", "Ana"]))
print(frequencias([]))Executa o código para ver a saída e os gráficos.
Segue a entrada ["Ana", "Bruno", "Ana", "Ana"]:
| Nome visitado | Estado antes | Atualização | Estado depois |
|---|---|---|---|
| Ana | {} | 0 + 1 | {"Ana": 1} |
| Bruno | {"Ana": 1} | 0 + 1 | {"Ana": 1, "Bruno": 1} |
| Ana | {"Ana": 1, "Bruno": 1} | 1 + 1 | {"Ana": 2, "Bruno": 1} |
| Ana | {"Ana": 2, "Bruno": 1} | 2 + 1 | {"Ana": 3, "Bruno": 1} |
Depois de cada iteração, o dicionário conta todas as ocorrências já visitadas. Ana fica com 3 e Bruno com 1; a lista vazia produz {}. A função não altera a lista recebida.
Para ordenar a apresentação, percorre sorted(contagem). A ordenação não é necessária para a contagem, só para escolher a ordem de saída.
Dicionários com coleções como valores
Um dicionário pode agrupar vários valores por chave. Se cada cadeira tem uma lista de notas, notas["FP"] pode ser [12, 16]. Alterar essa lista muda o conteúdo alcançável pelo dicionário. Cada chave deve receber uma lista própria: se duas chaves apontarem à mesma lista, um append será visível através de ambas.
notas = {"FP": [12, 16], "MD": [14]}
notas["FP"].append(18)
for cadeira, valores in notas.items():
print(cadeira, sum(valores) / len(valores))
A saída é FP 15.333333333333334 e MD 14.0. O cálculo exige listas não vazias; se permitires uma lista vazia, define primeiro o resultado nesse caso. Não acrescentes nem retires chaves durante uma iteração sobre o próprio dicionário.
Conjuntos e pertença
set guarda elementos únicos e hashable. set() cria o conjunto vazio; {} cria um dicionário. Um conjunto não tem índices e a sua ordem de iteração não deve determinar a resposta do programa.
python = {"Ana", "Bruno", "Carla"}
robotica = {"Bruno", "Duarte"}
print(sorted(python | robotica))
print(sorted(python & robotica))
print(sorted(python - robotica))
print(sorted(python ^ robotica))Executa o código para ver a saída e os gráficos.
Bruno pertence às duas atividades, por isso fica na interseção; a união recolhe os nomes das duas regiões uma só vez. Para python - robotica, conservamos apenas a parte de Python fora da sobreposição, Ana e Carla, enquanto a diferença simétrica conserva as duas partes exteriores e exclui Bruno.
| Operação | Pergunta | Resultado no exemplo |
|---|---|---|
A | B | Quem está em pelo menos uma atividade? | Ana, Bruno, Carla, Duarte |
A & B | Quem está em ambas? | Bruno |
A - B | Quem está em A e não em B? | Ana, Carla |
A ^ B | Quem está exatamente numa? | Ana, Carla, Duarte |
Usámos sorted para tornar a saída determinística. A diferença não é comutativa: robotica - python só contém Duarte. A <= B pergunta se A é subconjunto de B; A < B exige também que sejam diferentes. add acrescenta um elemento; discard retira-o se existir; remove falha se faltar. frozenset é a versão imutável e pode ser uma chave de dicionário.
Escolher a estrutura
Uma lista conserva ordem e repetições. Um tuplo representa uma sequência cujo recipiente não queremos alterar. Um conjunto conserva unicidade. Um dicionário conserva associações e permite contar ou agrupar.
Converter uma lista em conjunto perde a multiplicidade. Se recebeste ['Ana', 'Ana', 'Bruno'], o conjunto permite saber quem votou, mas não quantos votos houve.
Se o resultado exige a ordem original sem repetições, percorre a lista e usa um conjunto auxiliar para reconhecer os nomes já vistos, sem usar a ordem do conjunto como saída: começa com uma lista de resultado e um conjunto vazios, e quando um nome ainda não está no conjunto acrescenta-o às duas coleções, passando ao seguinte se já está. Assim a lista conserva a primeira ocorrência de cada nome.
Exercícios
Escolher uma chave
Qual dos valores pode ser uma chave de dicionário?
Pista
Mais uma pista
Solução
(2, 3) pode ser chave, porque os dois elementos são inteiros.
[2, 3] e (2, [3]) não podem.
(2, [3]). A lista interior impede que o tuplo seja hashable.(2, 3). É um tuplo de inteiros hashable.[2, 3]. Listas não são hashable.
Erros frequentes
Construir uma contagem
Obtém o dicionário de frequências para
[“azul”, “verde”, “azul”, “vermelho”, “verde”, “azul”]. Explica
o papel do zero inicial.
Pista
Mais uma pista
Solução
O resultado é {“azul”: 3, “verde”: 2, “vermelho”: 1}.
get(cor, 0) fornece zero quando a chave ainda falta, e a
atribuição guarda esse zero mais um.
Confere a tua resposta:
- Contei azul três vezes e verde duas.
- Contei vermelho uma vez.
- Expliquei que o valor por defeito não cria a chave sem atribuição.
Erros frequentes
Comparar grupos de inscrições
Duas atividades têm conjuntos de inscritos A = {1, 2, 3} e B = {3, 4}. Calcula A - B, B - A, A & B e A ^ B, explicando que inscritos seleciona cada operação. Se a lista original de A fosse [1, 1, 2, 3], conseguirias recuperar quantas inscrições fez a pessoa 1 a partir do conjunto?
Pista
Solução
A - B é {1, 2}, os inscritos apenas em A. B - A é {4}, os inscritos apenas em B, por isso a diferença tem direção. A & B é {3}, os inscritos em ambas. A ^ B é {1, 2, 4}, os inscritos exatamente numa.
A conversão de [1, 1, 2, 3] em conjunto elimina a segunda ocorrência de 1. O conjunto não permite recuperar essa contagem; uma lista ou um dicionário de frequências conserva a informação necessária.
Confere a tua resposta:
- Obtive {1, 2} e {4} nas diferenças.
- Obtive {3} na interseção e {1, 2, 4} na diferença simétrica.
- Expliquei a perda das repetições.
Conservar a primeira ocorrência
Define sem_repetidos(nomes): recebe uma lista de strings e devolve outra
lista sem repetições, pela ordem da primeira ocorrência. Conserva a lista
recebida. Maiúsculas e minúsculas distinguem nomes.
def sem_repetidos(nomes):
return []Verifica o código para comparar o resultado com os testes.
Testes
Ordem da primeira ocorrência
Código do testeprint(sem_repetidos(["Bruno", "Ana", "Bruno", "Carla", "Ana"]))Esperado:['Bruno', 'Ana', 'Carla']
Lista vazia
Código do testeprint(sem_repetidos([]))Esperado:[]
Uma string repetida
Código do testeprint(sem_repetidos(["FP", "FP", "FP"]))Esperado:['FP']
Maiúsculas distinguem nomes
Código do testeprint(sem_repetidos(["Ana", "ana", "Ana"]))Esperado:['Ana', 'ana']
Entrada preservada
Código do testenomes = ["B", "A", "B"] print(sem_repetidos(nomes)) print(nomes)Esperado:['B', 'A'] ['B', 'A', 'B']
print(sem_repetidos(["Bruno", "Ana", "Bruno", "Carla", "Ana"]))['Bruno', 'Ana', 'Carla']
print(sem_repetidos([]))[]
print(sem_repetidos(["FP", "FP", "FP"]))['FP']
print(sem_repetidos(["Ana", "ana", "Ana"]))['Ana', 'ana']
nomes = ["B", "A", "B"]
print(sem_repetidos(nomes))
print(nomes)['B', 'A'] ['B', 'A', 'B']
Pista
Precisas da ordem da lista e de reconhecer rapidamente o que já apareceu.
Mais uma pista
Mantém um conjunto de nomes vistos e uma lista de resultado. Acrescenta apenas quando o nome ainda falta no conjunto.
Solução
O conjunto decide se já vimos o nome. A lista regista a ordem em que cada nome apareceu pela primeira vez. Nenhuma operação altera nomes.
def sem_repetidos(nomes):
vistos = set()
resultado = []
for nome in nomes:
if nome not in vistos:
vistos.add(nome)
resultado.append(nome)
return resultadoErros frequentes
Usar sorted(set(nomes)), que muda a ordem, ou retirar elementos da lista
recebida durante o percurso.
Agrupar valores por chave
Define agrupar(pares): recebe uma lista de pares (chave, valor),
com chaves hashable, e devolve um dicionário com uma lista própria de valores
por chave, pela ordem de chegada. Não alteres a entrada. Com a lista vazia,
devolve um dicionário vazio. Por exemplo, [('FP', 12), ('MD', 14), ('FP', 16)]
produz {'FP': [12, 16], 'MD': [14]}.
def agrupar(pares):
return {}Verifica o código para comparar o resultado com os testes.
Testes
Duas chaves
Código do testeprint(agrupar([("FP", 12), ("MD", 14), ("FP", 16)]))Esperado:{'FP': [12, 16], 'MD': [14]}Vazio
Código do testeprint(agrupar([]))Esperado:{}Listas distintas por chave e entrada intacta
Código do testepares = [("FP", 12), ("MD", 14), ("FP", 12)] grupos = agrupar(pares) grupos["FP"].append(18) print(grupos) print(pares)Esperado:{'FP': [12, 12, 18], 'MD': [14]} [('FP', 12), ('MD', 14), ('FP', 12)]
print(agrupar([("FP", 12), ("MD", 14), ("FP", 16)])){'FP': [12, 16], 'MD': [14]}print(agrupar([])){}pares = [("FP", 12), ("MD", 14), ("FP", 12)]
grupos = agrupar(pares)
grupos["FP"].append(18)
print(grupos)
print(pares){'FP': [12, 12, 18], 'MD': [14]}
[('FP', 12), ('MD', 14), ('FP', 12)]Pista
Cria a lista na primeira ocorrência e acrescenta nas seguintes.
Mais uma pista
Testa com uma chave repetida não adjacente, como ("FP", 12) e depois ("FP", 16): sem o teste if chave not in grupos, a segunda ocorrência substitui a lista em vez de acrescentar.
Solução
Para cada par, se a chave falta, cria [valor]; senão, acrescenta o valor à lista existente.
def agrupar(pares):
grupos = {}
for chave, valor in pares:
if chave not in grupos:
grupos[chave] = []
grupos[chave].append(valor)
return gruposErros frequentes
Substituir a lista em vez de acrescentar (grupos[chave] = [valor] sempre); partilhar a mesma lista entre chaves com um valor por defeito mutável; alterar chaves durante a iteração do próprio dicionário.