Estruturas de dados
Organize e manipule seus dados com eficiência
Estruturas de dados em Python: listas, tuplas, dicionários e conjuntos
As estruturas de dados são formatos especializados para organizar, armazenar e manipular informações. O Python possui várias estruturas de dados nativas que facilitam o gerenciamento de coleções de elementos. Neste tutorial, vamos explorar as quatro principais estruturas de dados do Python: listas, tuplas, dicionários e conjuntos.
Entender essas estruturas e saber quando usar cada uma delas é essencial para escrever código Python eficiente e elegante.
Listas
As listas são uma das estruturas de dados mais versáteis e usadas com mais frequência em Python. Uma lista é uma coleção ordenada e mutável de elementos.
Criação de listas
# Lista vazia
ma_liste = []
# Lista com elementos
nombres = [1, 2, 3, 4, 5]
fruits = ["pomme", "banane", "orange"]
mixte = [1, "hello", 3.14, True] # Pode conter diferentes tipos
# Criação com a função list()
caracteres = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
Acesso aos elementos de uma lista
Os elementos de uma lista são indexados a partir de 0.
fruits = ["pomme", "banane", "orange", "fraise", "kiwi"]
# Acesso por índice positivo (a partir do início)
premier_fruit = fruits[0] # "pomme"
troisieme_fruit = fruits[2] # "orange"
# Acesso por índice negativo (a partir do fim)
dernier_fruit = fruits[-1] # "kiwi"
avant_dernier = fruits[-2] # "fraise"
# Fatiamento (slicing)
deux_premiers = fruits[0:2] # ["pomme", "banane"]
du_deuxieme_au_quatrieme = fruits[1:4] # ["banane", "orange", "fraise"]
tous_sauf_premier = fruits[1:] # ["banane", "orange", "fraise", "kiwi"]
trois_premiers = fruits[:3] # ["pomme", "banane", "orange"]
Modificação de listas
fruits = ["pomme", "banane", "orange"]
# Modificação de um elemento
fruits[1] = "poire" # ["pomme", "poire", "orange"]
# Adição de elementos
fruits.append("fraise") # Adiciona ao final: ["pomme", "poire", "orange", "fraise"]
fruits.insert(1, "kiwi") # Insere no índice 1: ["pomme", "kiwi", "poire", "orange", "fraise"]
fruits.extend(["cerise", "raisin"]) # Adiciona vários elementos: ["pomme", "kiwi", "poire", "orange", "fraise", "cerise", "raisin"]
# Remoção de elementos
fruits.remove("poire") # Remove a primeira ocorrência de "poire"
fruit_supprime = fruits.pop(2) # Remove e retorna o elemento no índice 2
del fruits[0] # Remove o elemento no índice 0
fruits.clear() # Esvazia a lista completamente
Operações comuns em listas
nombres = [3, 1, 4, 1, 5, 9, 2, 6]
# Tamanho
longueur = len(nombres) # 8
# Mínimo e máximo
minimum = min(nombres) # 1
maximum = max(nombres) # 9
# Soma
total = sum(nombres) # 31
# Ordenação
nombres.sort() # Ordena a lista in-place: [1, 1, 2, 3, 4, 5, 6, 9]
nombres_decroissants = sorted(nombres, reverse=True) # Retorna uma nova lista ordenada: [9, 6, 5, 4, 3, 2, 1, 1]
# Inversão
nombres.reverse() # Inverte a lista in-place
# Contagem
occurrences_de_1 = nombres.count(1) # 2
# Busca de índice
premier_index_de_1 = nombres.index(1) # Índice da primeira ocorrência de 1
List comprehension
A list comprehension é uma sintaxe concisa para criar listas a partir de sequências existentes.
# Sem list comprehension
carres = []
for x in range(10):
carres.append(x**2)
# Com list comprehension
carres = [x**2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Com condição
nombres_pairs = [x for x in range(20) if x % 2 == 0] # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# List comprehension mais complexa
matrice = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
aplati = [nombre for ligne in matrice for nombre in ligne] # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Casos de uso comuns das listas
As listas são ideais quando você precisa:
- Armazenar uma coleção de elementos em uma ordem específica
- Modificar a coleção com frequência (adicionar/remover elementos)
- Acessar os elementos pela posição
- Armazenar duplicatas
Exemplos de aplicações:
- Lista de tarefas
- Histórico de ações
- Filas de processamento
- Séries temporais de dados
Tuplas
As tuplas são semelhantes às listas, mas são imutáveis (não podem ser modificadas após a criação). Essa característica as torna mais eficientes em termos de memória e desempenho.
Criação de tuplas
# Tupla vazia
mon_tuple = ()
# Tupla com um único elemento (exige uma vírgula)
singleton = (42,)
# Tupla com vários elementos
coordonnees = (10, 20)
personne = ("Alice", 30, "Ingénieure")
# Criação sem parênteses (packing)
autre_tuple = 1, 2, 3
# Criação com a função tuple()
caracteres = tuple("Python") # ('P', 'y', 't', 'h', 'o', 'n')
Acesso aos elementos de uma tupla
O acesso aos elementos de uma tupla funciona como nas listas:
coordonnees = (10, 20, 30, 40, 50)
x = coordonnees[0] # 10
y = coordonnees[1] # 20
dernier = coordonnees[-1] # 50
sous_tuple = coordonnees[1:4] # (20, 30, 40)
Desempacotamento de tupla (unpacking)
Uma característica particularmente útil das tuplas é o desempacotamento:
# Desempacotamento básico
point = (10, 20)
x, y = point # x = 10, y = 20
# Desempacotamento com * para capturar vários elementos
premier, *milieu, dernier = (1, 2, 3, 4, 5)
# premier = 1, milieu = [2, 3, 4], dernier = 5
# Troca de variáveis
a, b = 1, 2
a, b = b, a # a = 2, b = 1
Operações em tuplas
coordonnees = (10, 20, 30)
plus_dimensions = coordonnees + (40, 50) # Concatenação: (10, 20, 30, 40, 50)
repete = coordonnees * 2 # Repetição: (10, 20, 30, 10, 20, 30)
Métodos de tupla
Como as tuplas são imutáveis, elas têm menos métodos do que as listas:
nombres = (3, 1, 4, 1, 5, 9, 2)
longueur = len(nombres) # 7
occurrences_de_1 = nombres.count(1) # 2
premier_index_de_1 = nombres.index(1) # 1
Casos de uso comuns das tuplas
As tuplas são preferíveis quando:
- Você tem uma coleção de elementos que não vai mudar
- Você quer evitar a modificação acidental dos dados
- Você precisa de uma chave hashable para dicionários (listas não podem ser chaves)
- Você retorna vários valores de uma função
Exemplos de aplicações:
- Coordenadas (x, y, z)
- Registros de banco de dados
- Chaves compostas
- Valores RGB de cores
- Parâmetros de configuração fixos
Dicionários
Os dicionários são coleções não ordenadas de pares chave-valor. Eles permitem acesso, inserção e remoção rápidos de elementos por chave.
Criação de dicionários
# Dicionário vazio
mon_dict = {}
# Dicionário com pares chave-valor
etudiant = {
"nom": "Martin",
"age": 22,
"cours": ["Mathématiques", "Informatique", "Physique"]
}
# Criação com a função dict()
coordonnees = dict(x=10, y=20, z=30)
# Criação a partir de tuplas chave-valor
items = [("pomme", 3), ("banane", 5), ("orange", 2)]
inventaire = dict(items)
# Dicionário por compreensão
carre_des_nombres = {x: x**2 for x in range(6)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Acesso e modificação de valores
etudiant = {
"nom": "Martin",
"age": 22,
"cours": ["Mathématiques", "Informatique", "Physique"]
}
# Acesso aos valores
nom = etudiant["nom"] # "Martin"
# Modificação dos valores
etudiant["age"] = 23
etudiant["cours"].append("Chimie")
# Acesso seguro com get()
telephone = etudiant.get("telephone") # None porque a chave não existe
telephone = etudiant.get("telephone", "Non spécifié") # Valor padrão "Non spécifié"
# Adição de um novo par chave-valor
etudiant["email"] = "martin@example.com"
# Remoção
cours = etudiant.pop("cours") # Remove e retorna o valor
del etudiant["age"] # Remove o par chave-valor
etudiant.clear() # Esvazia o dicionário
Métodos de dicionário
produits = {
"pomme": 0.80,
"banane": 0.50,
"orange": 0.70
}
# Obtenção de chaves, valores e itens
cles = produits.keys() # dict_keys(['pomme', 'banane', 'orange'])
valeurs = produits.values() # dict_values([0.8, 0.5, 0.7])
items = produits.items() # dict_items([('pomme', 0.8), ('banane', 0.5), ('orange', 0.7)])
# Atualização do dicionário
nouveaux_produits = {"kiwi": 1.20, "banane": 0.55}
produits.update(nouveaux_produits) # Adiciona "kiwi" e modifica o preço de "banane"
# Verificação da existência de uma chave
if "pomme" in produits:
print("Le prix des pommes est", produits["pomme"])
Iteração sobre dicionários
produits = {
"pomme": 0.80,
"banane": 0.50,
"orange": 0.70
}
# Iteração sobre as chaves (comportamento padrão)
for produit in produits:
print(produit)
# Iteração sobre os valores
for prix in produits.values():
print(prix)
# Iteração sobre os pares chave-valor
for produit, prix in produits.items():
print(f"{produit}: {prix}€")
Dicionários aninhados
# Dicionário de dicionários
etudiants = {
"101": {
"nom": "Alice",
"age": 22,
"cours": ["Math", "Informatique"]
},
"102": {
"nom": "Bob",
"age": 21,
"cours": ["Physique", "Biologie"]
}
}
# Acesso aos elementos aninhados
nom_etudiant_102 = etudiants["102"]["nom"] # "Bob"
cours_alice = etudiants["101"]["cours"][1] # "Informatique"
Casos de uso comuns dos dicionários
Os dicionários são perfeitos para:
- Coleções em que você busca elementos por uma chave única
- Armazenamento de dados associativos (pares chave-valor)
- Tabelas de hash rápidas para busca
- Representação de objetos com atributos
Exemplos de aplicações:
- Cache de dados
- Contagem de frequências
- Bancos de dados em memória
- Parâmetros de configuração
- Armazenamento de propriedades de objetos JSON
Conjuntos
Os conjuntos (sets) são coleções não ordenadas de elementos únicos. Eles são otimizados para testar pertencimento e eliminar duplicatas.
Criação de conjuntos
# Conjunto vazio (não é possível usar {}, pois isso cria um dicionário vazio)
mon_ensemble = set()
# Conjunto com elementos
couleurs = {"rouge", "vert", "bleu"}
# Criação a partir de uma lista (elimina as duplicatas)
nombres = set([1, 2, 2, 3, 4, 4, 5]) # {1, 2, 3, 4, 5}
# Conjunto por compreensão
nombres_pairs = {x for x in range(10) if x % 2 == 0} # {0, 2, 4, 6, 8}
Operações em conjuntos
fruits1 = {"pomme", "banane", "orange"}
fruits2 = {"orange", "kiwi", "ananas"}
# Adição e remoção de elementos
fruits1.add("fraise")
fruits1.remove("banane") # Lança um erro se o elemento não existir
fruits1.discard("cerise") # Não lança erro se o elemento não existir
element = fruits1.pop() # Remove e retorna um elemento arbitrário
fruits1.clear() # Esvazia o conjunto
# Verificação de pertencimento
"orange" in fruits2 # True
"banane" in fruits2 # False
Operações de teoria dos conjuntos
Os conjuntos permitem realizar operações matemáticas de teoria dos conjuntos:
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}
# União (elementos que estão em A OU em B)
union = A | B # {1, 2, 3, 4, 5, 6, 7, 8}
# ou
union = A.union(B)
# Interseção (elementos que estão em A E em B)
intersection = A & B # {4, 5}
# ou
intersection = A.intersection(B)
# Diferença (elementos que estão em A mas NÃO em B)
difference = A - B # {1, 2, 3}
# ou
difference = A.difference(B)
# Diferença simétrica (elementos que estão em A OU B mas NÃO em ambos)
diff_sym = A ^ B # {1, 2, 3, 6, 7, 8}
# ou
diff_sym = A.symmetric_difference(B)
# Verificar se um conjunto é subconjunto de outro
est_sous_ensemble = {1, 2}.issubset(A) # True
# Verificar se um conjunto é superconjunto de outro
est_sur_ensemble = A.issuperset({1, 2}) # True
Conjuntos congelados (frozenset)
Um frozenset é um conjunto imutável, o que o torna hashable. Ele pode ser usado como chave de dicionário.
# Criação de um frozenset
couleurs_primaires = frozenset(["rouge", "bleu", "jaune"])
# Uso como chave de dicionário
groupes_couleurs = {
frozenset(["rouge", "bleu", "jaune"]): "primaires",
frozenset(["violet", "orange", "vert"]): "secondaires"
}
Casos de uso comuns dos conjuntos
Os conjuntos são particularmente úteis para:
- Eliminar duplicatas de uma coleção
- Testar rapidamente o pertencimento de elementos
- Realizar operações matemáticas com conjuntos
- Encontrar os elementos únicos ou comuns entre coleções
Exemplos de aplicações:
- Filtragem de duplicatas
- Análise de dados únicos
- Operações em conjuntos de tags
- Gerenciamento de permissões de usuários
- Representação de grafos (teoria dos conjuntos)
Escolhendo a estrutura de dados certa
Cada estrutura de dados tem seus pontos fortes e fracos. Veja uma tabela comparativa para ajudar você a escolher a mais adequada à sua necessidade:
| Estrutura | Ordenada | Mutável | Elementos indexados | Elemento chave-valor | Elementos únicos | Caso de uso típico |
|---|---|---|---|---|---|---|
| Lista | ✅ | ✅ | ✅ | ❌ | ❌ | Coleção ordenada mutável |
| Tupla | ✅ | ❌ | ✅ | ❌ | ❌ | Coleção ordenada imutável |
| Dicionário | ❌¹ | ✅ | ❌ | ✅ | ✅ (chaves) | Mapeamento chave-valor |
| Conjunto | ❌ | ✅ | ❌ | ❌ | ✅ | Eliminação de duplicatas, operações de conjuntos |
¹ Desde o Python 3.7, os dicionários preservam a ordem de inserção, mas não são considerados intrinsecamente ordenados em seu design.
Dicas para escolher a estrutura adequada
- Use uma lista quando a ordem e a modificação forem importantes
- Use uma tupla para dados somente leitura ou chaves compostas
- Use um dicionário para buscas rápidas por chave
- Use um conjunto quando a unicidade e as operações de conjuntos forem necessárias
Exemplo prático: análise de dados
Veja um exemplo que usa todas as estruturas de dados para analisar vendas:
# Dados de vendas (produto, quantidade, preço, categoria)
ventes = [
("pomme", 5, 0.80, "fruits"),
("banane", 3, 0.50, "fruits"),
("pain", 2, 1.20, "boulangerie"),
("lait", 1, 0.90, "laitier"),
("fromage", 2, 2.50, "laitier"),
("pomme", 2, 0.80, "fruits"),
("yaourt", 4, 0.60, "laitier")
]
# Uso de dicionários para agrupar por categoria
ventes_par_categorie = {}
for produit, quantite, prix, categorie in ventes:
if categorie not in ventes_par_categorie:
ventes_par_categorie[categorie] = []
ventes_par_categorie[categorie].append((produit, quantite, prix))
# Uso de conjuntos para obter produtos únicos
produits_uniques = {produit for produit, _, _, _ in ventes}
print(f"Produits uniques: {produits_uniques}") # {'pain', 'pomme', 'fromage', 'lait', 'yaourt', 'banane'}
# Uso de tuplas para armazenar os totais por categoria (imutável)
totaux_par_categorie = tuple(
(categorie, sum(quantite * prix for _, quantite, prix in produits))
for categorie, produits in ventes_par_categorie.items()
)
print("Totaux par catégorie:", totaux_par_categorie)
# Uso de listas e de compreensões para analisar as vendas
total_par_produit = {}
for produit, quantite, prix, _ in ventes:
if produit in total_par_produit:
total_par_produit[produit] = (total_par_produit[produit][0] + quantite, prix)
else:
total_par_produit[produit] = (quantite, prix)
# Lista ordenada dos produtos por faturamento
ca_par_produit = [(produit, quantite * prix) for produit, (quantite, prix) in total_par_produit.items()]
ca_par_produit.sort(key=lambda x: x[1], reverse=True)
print("Produits par chiffre d'affaires:")
for produit, ca in ca_par_produit:
print(f"- {produit}: {ca:.2f}€")
Desempenho das estruturas de dados
Em aplicações que manipulam grandes quantidades de dados, o desempenho se torna importante. Veja um panorama das complexidades de tempo das operações mais comuns:
| Operação | Lista | Tupla | Dicionário | Conjunto |
|---|---|---|---|---|
| Acesso por índice | O(1) | O(1) | N/A | N/A |
| Acesso por chave | N/A | N/A | O(1)¹ | N/A |
| Busca de elemento | O(n) | O(n) | O(1)¹ | O(1)¹ |
| Adição de elemento | O(1)² | N/A | O(1)¹ | O(1)¹ |
| Remoção de elemento | O(n) | N/A | O(1)¹ | O(1)¹ |
¹ Em média. No pior caso (raro), essas operações podem ser O(n).
² A adição no final é O(1) amortizado. A inserção no meio é O(n).
Conclusão
As estruturas de dados são os blocos de construção fundamentais de qualquer programa Python. Entender suas características, vantagens e desvantagens ajudará você a escrever um código mais eficiente e mais elegante.
Para recapitular:
- As listas são versáteis e mutáveis, perfeitas para coleções ordenadas
- As tuplas são imutáveis e eficientes, ideais para dados somente leitura
- Os dicionários oferecem buscas rápidas por chave, perfeitos para associações
- Os conjuntos eliminam duplicatas e permitem operações de conjuntos eficientes
Escolher a estrutura certa para cada situação é uma habilidade que melhora com a prática. Não hesite em experimentar diferentes abordagens para resolver seus problemas com dados.
No próximo tutorial, vamos explorar as funções em Python e como usá-las para organizar e reutilizar seu código de forma eficiente.
Precisa revisar o básico de Python? Confira nosso tutorial sobre os fundamentos da sintaxe Python.
Comentários
Os comentários são alimentados pelo GitHub Discussions
Conecte-se com o GitHub para participar da discussão