Esta seção corresponde a Lists, Tuples, Dictionaries, defaultdict, Counters e Sets, do capítulo 2 de Grus (2019).
DicaO que esta seção assume
Que você conhece arranjos, listas, tabelas hash e conjuntos como estruturas. O que muda: a sintaxe de fatiamento e desempacotamento. O que vale desacelerar: defaultdict e Counter — os dois substituem laços de contagem que você provavelmente escreveria à mão, e por isso aparecem em quase todo capítulo daqui em diante.
Listas e tuplas, rapidamente
Lista é a estrutura ordenada e mutável de Python. O que difere do que você já conhece é o fatiamento, que é mais expressivo que o usual:
O operador in verifica pertinência — e é linear numa lista, o que importa quando a lista é grande:
1in [1, 2, 3], 0in [1, 2, 3]
(True, False)
Tuplas são listas imutáveis: quase tudo que se faz com lista se faz com tupla, menos modificar. Elas servem para retornar múltiplos valores e para desempacotamento:
def sum_and_product(x, y):return (x + y), (x * y)sp = sum_and_product(2, 3)s, p = sum_and_product(5, 10)# desempacotamento também troca valores sem variável temporáriax, y =1, 2x, y = y, xsp, s, p, (x, y)
((5, 6), 15, 50, (2, 1))
Nota
x, y = y, x não é truque: o lado direito é avaliado por inteiro antes da atribuição, então a troca é atômica do ponto de vista de quem lê. Aparece bastante em código numérico.
Dicionários
Dicionário associa chaves a valores, com busca rápida. A parte que vale atenção é o que acontece quando a chave não existe:
grades = {"Joel": 80, "Tim": 95}# [] levanta KeyError se a chave não existetem_joel ="Joel"in gradestem_kate ="Kate"in grades# .get() devolve um padrão em vez de estourarnota_joel = grades.get("Joel", 0)nota_kate = grades.get("Kate", 0) # 0, o padrãosem_nota = grades.get("Ninguém") # None, o padrão do padrãotem_joel, tem_kate, nota_joel, nota_kate, sem_nota
(True, False, 80, 0, None)
Dicionários têm keys(), values() e items(), e este último é o que você vai ver o tempo todo:
for nome, nota in grades.items():print(f"{nome}: {nota}")
Joel: 80
Tim: 95
defaultdict
Aqui vale desacelerar.
Imagine contar palavras de um documento. A versão sem defaultdict exige tratar o caso “chave ainda não existe” toda vez:
document = ["o", "rato", "roeu", "a", "roupa", "do", "rei", "de", "roma","o", "rei", "de", "roma", "roeu", "o", "rato"]# versão 1: checar antesword_counts = {}for word in document:if word in word_counts: word_counts[word] +=1else: word_counts[word] =1word_counts
Um defaultdict faz isso sozinho: quando você acessa uma chave que não existe, ele a cria usando a função que você deu na construção.
from collections import defaultdictword_counts = defaultdict(int) # int() devolve 0for word in document: word_counts[word] +=1# sem checagem: a chave nasce com 0dict(word_counts)
defaultdict(list) é o padrão de agrupar — para cada chave, acumular uma lista de coisas.
Você já viu esse padrão duas vezes no Capítulo 1, sem que ele fosse nomeado: montando o índice de interesses por usuário e o de usuários por interesse, e agrupando salários por faixa de tempo de casa.
Sempre que você for escrever “para cada X, junte os Y correspondentes”, é defaultdict(list).
Counter
Counter transforma uma sequência em um dicionário de chave para contagem. É o caso de contar palavras, resolvido numa linha:
from collections import Counterword_counts = Counter(document)word_counts
O método que faz Counter valer a pena é o most_common:
for word, count in word_counts.most_common(3):print(word, count)
o 3
rato 2
roeu 2
Counter é um defaultdict(int) com contagem automática e ordenação embutida. As três versões da contagem de palavras acima produzem o mesmo resultado — a diferença é quantas decisões você precisou tomar para chegar lá.
Na votação do k-vizinhos mais próximos, é o Counter que decide o rótulo: Counter(labels).most_common(1) devolve o mais votado.
Conjuntos
set guarda elementos distintos, e a razão de usá-lo raramente é a distinção — é a velocidade:
in numa lista percorre os elementos até achar o que procura; num conjunto é uma consulta direta, que não depende do tamanho.
Os tempos exatos acima variam a cada execução — é uma medição de relógio, não um cálculo determinístico. O que não varia é a forma: dobre o tamanho da lista e o tempo dela dobra junto; dobre o do conjunto e o tempo fica o mesmo. Com cem mil elementos a diferença já é grande o bastante para aparecer; com dez milhões, a busca linear deixa de ser viável.
É exatamente por isso que, no Capítulo 1, o índice de amizades virou um dicionário em vez de continuar sendo uma lista de pares.
Grus, Joel. 2019. Data Science from Scratch: First Principles with Python. 2nd ed. O’Reilly Media.