5  Estruturas de Dados

Até aqui, já usamos variáveis simples, decisões, laços e funções. Agora vamos organizar melhor os dados usando estruturas de dados.

Neste capítulo vamos estudar:

A ordem foi escolhida pela praticidade: começamos pelas estruturas que aparecem com mais frequência em problemas reais.


5.1 Listas: revisão e operações importantes

Uma lista guarda múltiplos valores em ordem. Ela é mutável, ou seja, podemos alterar seus elementos.

Exemplos Práticos:

(lista_compras.py): Introduz crescimento de listas com .append() e iteração simples com for. O problema do exemplo é montar uma lista de materiais de bancada.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para montar uma lista de materiais de bancada.
#
# O programa deve funcionar da seguinte maneira:
# 1. Comece com uma lista vazia chamada materiais.
# 2. Exiba uma mensagem informando que os itens serao adicionados.
# 3. Use append para adicionar tres materiais a lista.
# 4. Exiba uma mensagem de lista final.
# 5. Percorra a lista com for e imprima cada material em uma linha.
#
# Casos especiais:
# 1. O foco do exemplo e mostrar a construcao da lista com append.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Lista e uma colecao em ordem usada quando queremos armazenar varios itens
# e manter a sequencia em que eles foram adicionados.
# O metodo append coloca um novo valor no final da lista, sem substituir os anteriores.
# A lista inicia vazia e vai crescendo com append.
materiais = []

print("Adicionando materiais para a bancada de teste...")

# Cada append coloca um novo item no final da lista.
materiais.append("multimetro")
materiais.append("sensor de temperatura")
materiais.append("cabos de medicao")

print("Lista final de materiais:")

# O for percorre a lista um elemento por vez.
# Em listas, essa iteracao respeita a ordem de insercao, o que ajuda quando
# queremos processar os itens na mesma sequencia em que foram registrados.
for material in materiais:
    print(f"Material necessario: {material}")

(operacoes_lista.py): Introduz criação de lista, acesso por índice e remoção com pop(). O problema do exemplo é registrar três leituras de pressão e depois descartar a última medição.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para praticar operacoes basicas com listas.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. Tres leituras de pressao em bar (numeros inteiros).
#
# O programa deve funcionar da seguinte maneira:
# 1. Crie uma lista vazia para armazenar as leituras.
# 2. Leia as tres pressões e adicione cada valor com append.
# 3. Exiba a lista completa, a primeira leitura e a ultima leitura.
# 4. Remova a ultima leitura com pop e exiba a lista atualizada.
#
# Casos especiais:
# 1. O foco do exercicio e praticar criacao, acesso e remocao em listas.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Lista e uma estrutura que guarda varios valores em ordem.
# Essa ordem importa: o primeiro valor inserido fica na primeira posicao,
# o segundo fica na segunda, e assim por diante.
# Lista tambem e mutavel, ou seja, pode crescer, encolher e ter elementos alterados.
# A lista comeca vazia e vai recebendo as leituras digitadas.
leituras_pressao = []

# O laco repete a leitura tres vezes.
for i in range(3):
    pressao = int(input(f"Digite a {i + 1}a leitura de pressao em bar: "))
    leituras_pressao.append(pressao)

# Em listas, cada elemento tem uma posicao chamada indice.
# O indice 0 acessa o primeiro elemento.
# O indice -1 acessa o ultimo elemento, contando a partir do fim.
print(f"Leituras registradas: {leituras_pressao}")
print(f"Primeira leitura: {leituras_pressao[0]}")
print(f"Ultima leitura: {leituras_pressao[-1]}")

# pop remove o ultimo elemento da lista e modifica a estrutura original.
# Esse tipo de operacao mostra por que dizemos que listas sao mutaveis.
leituras_pressao.pop()
print(f"Lista apos remover a ultima leitura: {leituras_pressao}")

(aplicador_operacoes_lista.py): Introduz a ideia de lista como estrutura mutável, que pode ser alterada ao longo do programa. O problema do exemplo é acompanhar as mudanças em uma sequência de etapas de montagem.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para aplicar varias operacoes em uma lista de etapas.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Comece com uma lista de tres etapas de montagem.
# 2. Adicione duas novas etapas com append.
# 3. Remova a ultima etapa com pop.
# 4. Altere a primeira etapa da lista.
# 5. Exiba o estado da lista apos cada operacao.
#
# Casos especiais:
# 1. O foco e rastrear como a estrutura muda ao longo do programa.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Este exemplo reforca que listas sao mutaveis.
# Isso significa que o mesmo objeto em memoria pode ser alterado ao longo do programa:
# podemos adicionar elementos, remover elementos e trocar um valor em uma posicao especifica.
# Essa caracteristica torna listas muito uteis para dados que evoluem com o tempo.
# A lista representa a sequencia de etapas de um processo.
etapas_montagem = ["cortar chapa", "furar base", "montar suporte"]
print(f"Lista inicial: {etapas_montagem}")

# Novas etapas sao acrescentadas ao final da lista.
etapas_montagem.append("instalar sensor")
print(f"Apos append('instalar sensor'): {etapas_montagem}")

etapas_montagem.append("inspecionar conjunto")
print(f"Apos append('inspecionar conjunto'): {etapas_montagem}")

# pop remove a ultima etapa da lista.
etapas_montagem.pop()
print(f"Apos pop(): {etapas_montagem}")

# Um elemento existente tambem pode ser alterado por indice.
etapas_montagem[0] = "receber chapa"
print(f"Apos alterar a primeira etapa: {etapas_montagem}")

5.2 Dicionários: chave e valor

Dicionários são ideais quando queremos acessar informações por nome ou identificador.

Exemplos Práticos:

(sistema_estoque.py): Introduz dicionários como pares chave-valor e consulta por chave. O problema do exemplo é registrar códigos de materiais e consultar quantidades em estoque.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para controlar um estoque simples com dicionario.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. O codigo de tres materiais (texto).
# 2. A quantidade de cada material (inteiro).
# 3. O codigo de um material para consulta.
#
# O programa deve funcionar da seguinte maneira:
# 1. Armazene os codigos e quantidades em um dicionario.
# 2. Exiba o dicionario completo.
# 3. Consulte o codigo informado e mostre a quantidade correspondente.
# 4. Se o codigo nao existir, exiba uma mensagem informando isso.
#
# Casos especiais:
# 1. O dicionario deve permitir acesso rapido pela chave digitada.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Dicionario organiza dados em pares chave-valor.
# A chave funciona como um identificador unico usado para localizar um dado.
# O valor e a informacao associada a essa chave.
# Diferente da lista, aqui o acesso principal nao acontece por posicao,
# mas pelo nome ou codigo que identifica cada item.
# Cada chave do dicionario sera um codigo de material.
estoque = {}

# O laco coleta tres pares de chave e valor.
for i in range(3):
    codigo = input(f"Digite o codigo do {i + 1}o material: ")
    quantidade = int(input("Digite a quantidade em estoque: "))
    estoque[codigo] = quantidade

# A consulta em dicionario acontece pela chave, nao por posicao.
# Isso faz sentido quando o problema pede "encontre a quantidade do codigo X"
# e nao "me mostre o elemento da posicao 2".
codigo_procurado = input("Qual codigo deseja consultar? ")

print(f"Estoque completo: {estoque}")

# O if testa se a chave existe antes do acesso.
# Esse cuidado e importante porque tentar acessar uma chave inexistente gera erro.
if codigo_procurado in estoque:
    print(f"Quantidade de '{codigo_procurado}': {estoque[codigo_procurado]}")
else:
    print("Codigo nao encontrado no estoque.")

(contador_frequencia_palavras.py): Introduz o uso de dicionário para contagem acumulada. O problema do exemplo é medir a frequência de termos em um relatório de inspeção.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para contar a frequencia de palavras em um relatorio.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. Uma observacao de inspeção em texto.
#
# O programa deve funcionar da seguinte maneira:
# 1. Converta o texto para letras minusculas.
# 2. Separe as palavras por espaco usando split.
# 3. Use um dicionario para contar quantas vezes cada palavra aparece.
# 4. Exiba o dicionario final de frequencias.
#
# Casos especiais:
# 1. Ignore espacos extras no inicio e no fim do texto.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# strip remove espacos extras das extremidades.
relatorio = input("Digite a observacao de inspecao: ").strip().lower()
palavras = relatorio.split()

# O dicionario guardara a contagem de cada palavra.
# Aqui vemos um padrao muito comum: usar a propria palavra como chave
# e usar o numero de ocorrencias como valor.
# Sempre que a palavra reaparece, seu valor no dicionario e atualizado.
frequencia = {}

# Cada palavra encontrada atualiza sua contagem no dicionario.
# get(palavra, 0) significa: pegue a contagem atual da palavra;
# se ela ainda nao existir, comece em 0.
for palavra in palavras:
    frequencia[palavra] = frequencia.get(palavra, 0) + 1

print(f"Frequencia de palavras: {frequencia}")

(analisador_texto_palavras.py): Introduz dicionários como resumo final de categorias observadas. O problema do exemplo é consolidar etiquetas de ocorrência registradas em um turno.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para analisar etiquetas de ocorrencias em um turno.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Use uma frase fixa com etiquetas repetidas de ocorrencia.
# 2. Separe as palavras dessa frase.
# 3. Conte a frequencia de cada etiqueta em um dicionario.
# 4. Exiba o dicionario final com as contagens.
#
# Casos especiais:
# 1. Trabalhe em minusculas para evitar duplicidade por diferenca de caixa.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# A frase representa etiquetas registradas ao longo de um turno.
ocorrencias_turno = "alarme temperatura pressao alarme vazamento pressao alarme"
palavras = ocorrencias_turno.lower().split()

# O dicionario final mostrara quantas vezes cada etiqueta apareceu.
# Isso ilustra um uso comum de dicionarios: resumir uma colecao grande de dados
# em um mapa de categorias e quantidades.
# Em vez de guardar cada ocorrencia separadamente para sempre,
# passamos a guardar um resumo por chave.
frequencia = {}
for palavra in palavras:
    frequencia[palavra] = frequencia.get(palavra, 0) + 1

print(f"Ocorrencias no turno: {frequencia}")

5.3 Conjuntos: elementos únicos

Conjuntos (set) guardam valores sem repetição. São úteis para deduplicar dados e testar pertencimento.

Exemplos Práticos:

(gerenciador_convidados.py): Introduz conjuntos como estrutura de elementos únicos. O problema do exemplo é eliminar códigos de peças duplicados e consultar se um código está presente.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para remover codigos duplicados de um lote de pecas.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. Cinco codigos de pecas (texto), podendo repetir.
# 2. Um codigo para consulta.
#
# O programa deve funcionar da seguinte maneira:
# 1. Armazene os codigos digitados em uma lista.
# 2. Converta a lista para conjunto para remover duplicatas.
# 3. Exiba a lista original e o conjunto final.
# 4. Informe se o codigo consultado esta presente no conjunto.
#
# Casos especiais:
# 1. Use letras minusculas para padronizar a comparacao.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# A lista pode conter repeticoes porque registra cada codigo digitado exatamente
# como o usuario informou, inclusive quando um mesmo codigo aparece mais de uma vez.
# Depois, o conjunto entra em cena para manter apenas valores unicos.
# Essa e a principal ideia de set: representar pertencimento sem repetir elementos.
codigos_pecas = []

for i in range(5):
    codigo = input(f"Digite o {i + 1}o codigo de peca: ").lower()
    codigos_pecas.append(codigo)

# Conjunto e uma estrutura sem repeticao.
# Ao converter a lista para conjunto, os valores repetidos desaparecem automaticamente.
# Isso e util quando o problema nao quer contar repeticoes, mas apenas saber
# quais elementos distintos existem.
codigos_unicos = set(codigos_pecas)

codigo_procurado = input("Digite um codigo para consulta: ").lower()

print(f"Lista original: {codigos_pecas}")
print(f"Conjunto sem duplicatas: {codigos_unicos}")
print(f"'{codigo_procurado}' esta no conjunto? {codigo_procurado in codigos_unicos}")

(limpador_de_texto.py): Introduz teste de pertencimento com in sobre uma coleção de caracteres. O problema do exemplo é limpar um texto de observação técnica removendo pontuações.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Desenvolva um programa para limpar um texto de observacao tecnica.

#

# O programa deve funcionar da seguinte maneira:

# 1. Defina um texto original com pontuacoes.

# 2. Defina uma string com os caracteres de pontuacao que serao removidos.

# 3. Crie uma string vazia para acumular o texto limpo.

# 4. Percorra o texto original caractere por caractere com um for.

# 5. Se o caractere estiver na string de pontuacao, exiba uma mensagem e use

#    continue para pular para o proximo caractere.

# 6. Caso contrario, adicione o caractere ao texto limpo.

# 7. Exiba o texto original e o texto final sem pontuacao.

#

# Casos especiais:

# 1. O foco e praticar teste de pertencimento com o operador in.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-



# Texto de observacao que sera limpo caractere por caractere.

texto_original = "Sensor-3, pressao alta! Reavaliar? Sim."



# A string pontuacao guarda os caracteres que devem ser descartados.

# Neste exemplo, ela funciona como uma pequena colecao de referencia.

# O conceito novo aqui e o teste de pertencimento: perguntar se um elemento

# faz parte ou nao de uma colecao.

# Esse mesmo raciocinio aparece depois com conjuntos e dicionarios.

pontuacao = ",.!?-"



# O resultado final sera montado aos poucos nesta variavel.

texto_limpo = ""



print(f"Texto original: {texto_original}")

print("Limpando o texto...")



for caractere in texto_original:

    # O operador in faz um teste de pertencimento.

    # Em outras palavras, ele responde a pergunta:

    # "o caractere atual aparece na colecao de pontuacoes?"
    # Se a resposta for verdadeira, o caractere nao entra no texto final.

    if caractere in pontuacao:

        print(f"Pontuacao '{caractere}' removida.")

        continue



    # Se nao for pontuacao, o caractere entra no texto limpo.

    texto_limpo = texto_limpo + caractere



print(f"Texto limpo: {texto_limpo}")

(operacoes_conjuntos.py): Introduz operações clássicas de conjuntos, como união, interseção e pertinência. O problema do exemplo é comparar os equipamentos disponíveis em duas bancadas.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para operar conjuntos de equipamentos.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina um conjunto com equipamentos da bancada A.
# 2. Defina um conjunto com equipamentos da bancada B.
# 3. Calcule a intersecao entre os conjuntos.
# 4. Calcule a uniao entre os conjuntos.
# 5. Verifique se um equipamento especifico pertence a bancada B.
# 6. Exiba todos os resultados.
#
# Casos especiais:
# 1. O foco e interpretar uniao, intersecao e pertinencia em conjuntos.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Conjunto guarda apenas valores unicos e nao depende de ordem.
# Ele e especialmente util quando queremos responder perguntas como:
# "quais elementos existem?", "quais aparecem em comum?" e
# "quais pertencem a este grupo?"
# Cada conjunto representa os equipamentos presentes em uma bancada.
bancada_a = {"multimetro", "sensor termico", "bomba de teste"}
bancada_b = {"sensor termico", "fonte dc", "cabos de medicao"}

# intersection devolve apenas os elementos em comum entre os conjuntos.
# Esse tipo de operacao e util quando queremos descobrir sobreposicoes.
intersecao = bancada_a.intersection(bancada_b)

# union junta todos os elementos sem repetir o que ja existe.
# O resultado continua sendo um conjunto, entao valores duplicados nao aparecem duas vezes.
uniao = bancada_a.union(bancada_b)

# O operador in testa pertencimento no conjunto escolhido.
multimetro_esta_na_b = "multimetro" in bancada_b

print(f"Intersecao entre as bancadas: {intersecao}")
print(f"Uniao das bancadas: {uniao}")
print(f"'multimetro' pertence a bancada B? {multimetro_esta_na_b}")

5.4 Tuplas: registros fixos

Tuplas são parecidas com listas, mas imutáveis. São úteis para representar dados fixos.

Exemplos Práticos:

(sistema_coordenadas.py): Introduz tuplas como registros fixos e o desempacotamento em variáveis. O problema do exemplo é representar um ponto de medição (x, y).

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para representar um ponto de medicao com tupla.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. O valor de x do ponto (decimal).
# 2. O valor de y do ponto (decimal).
#
# O programa deve funcionar da seguinte maneira:
# 1. Crie uma tupla chamada ponto_medicao com os valores (x, y).
# 2. Faca o desempacotamento da tupla em duas variaveis.
# 3. Exiba a tupla completa e as coordenadas separadas.
#
# Casos especiais:
# 1. O objetivo do exemplo e mostrar tupla como registro fixo.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Leitura das coordenadas do ponto.
x = float(input("Digite a coordenada x do ponto: "))
y = float(input("Digite a coordenada y do ponto: "))

# Tupla representa um registro fixo: os dados ficam agrupados e em ordem.
# Diferente da lista, a ideia aqui nao e ir adicionando ou removendo elementos,
# mas guardar um conjunto pequeno e estavel de campos relacionados.
# A tupla agrupa os dois valores em um unico registro.
ponto_medicao = (x, y)

# O desempacotamento separa novamente os valores da tupla em variaveis distintas.
# Isso facilita usar cada campo do registro com nomes mais claros no restante do codigo.
coordenada_x, coordenada_y = ponto_medicao

print(f"Ponto completo: {ponto_medicao}")
print(f"Coordenada x: {coordenada_x}")
print(f"Coordenada y: {coordenada_y}")

(media_temperaturas.py): Introduz combinação de estruturas, usando tuplas para registros fixos e listas para coleções mutáveis. O problema do exemplo é organizar leituras de sensores e calcular a média.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Desenvolva um programa para combinar tuplas e listas em um registro de sensores.
# O programa deve solicitar ao usuario as seguintes informacoes:
# 1. A temperatura de tres sensores (valores decimais).
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma tupla fixa com os nomes dos sensores.
# 2. Leia uma temperatura para cada sensor e monte uma lista de tuplas no formato
#    (nome_do_sensor, temperatura).
# 3. Percorra a lista de tuplas para somar as temperaturas.
# 4. Calcule e exiba a media geral.
# 5. Exiba tambem a lista final de registros.
#
# Casos especiais:
# 1. O objetivo do exemplo e mostrar como combinar diferentes estruturas.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# A tupla guarda nomes fixos dos sensores: esses identificadores nao mudam.
# Esse e um bom caso de uso de tupla, porque a estrutura representa um conjunto
# pequeno e estavel de valores que nao precisa ser alterado.
sensores = ("tanque_1", "tanque_2", "tanque_3")

# A lista vai guardar tuplas no formato (sensor, temperatura).
# Aqui combinamos duas estruturas:
# - tupla para cada registro fixo de leitura
# - lista para a colecao mutavel de registros gerados durante a execucao
registros = []

for sensor in sensores:
    temperatura = float(input(f"Digite a temperatura do sensor {sensor}: "))
    registros.append((sensor, temperatura))

soma = 0

# Cada item da lista e uma tupla, que pode ser desempacotada direto no for.
# Isso mostra que estruturas podem ser combinadas: uma lista pode conter varias tuplas.
for sensor, temperatura in registros:
    soma += temperatura

media = soma / len(registros)

print(f"Registros de temperatura: {registros}")
print(f"Media geral: {media}")

(desempacotador_tuplas.py): Introduz desempacotamento de tuplas com mais de dois campos. O problema do exemplo é separar os dados de um registro fixo de sensor em variáveis individuais.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para praticar desempacotamento de tupla.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma tupla fixa com tres dados de um sensor.
# 2. Desempacote a tupla em tres variaveis.
# 3. Exiba a tupla completa e os valores separados.
#
# Casos especiais:
# 1. O objetivo e mostrar como uma tupla pode representar um registro fixo.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Tuplas sao uteis quando os campos de um registro devem permanecer agrupados.
# Pense nelas como uma ficha com posicoes fixas: codigo, vazao e pressao.
# O numero de campos e conhecido, e cada posicao tem um significado especifico.
# Esta tupla representa um registro fixo de medicao.
registro_sensor = ("sensor_7", 18.5, 4.2)

# O desempacotamento separa cada campo do registro.
codigo_sensor, vazao, pressao = registro_sensor

print(f"Tupla completa: {registro_sensor}")
print(f"Codigo do sensor: {codigo_sensor}")
print(f"Vazao: {vazao}")
print(f"Pressao: {pressao}")

5.5 Iterando sobre estruturas de dados

Todas as estruturas que estudamos podem ser percorridas com for. Cada tipo tem formas idiomáticas de iteração:

  • Listas e tuplas: for elemento in sequencia percorre os valores. Use enumerate() quando precisar do índice junto com o valor.
  • Dicionários: for chave, valor in dicionario.items() percorre pares. Use .keys() para só chaves ou .values() para só valores.
  • Conjuntos: for elemento in conjunto percorre os valores (a ordem não é garantida).

Exemplo Prático (iteracao_estruturas.py): Introduz as formas idiomáticas de iteração sobre listas, dicionários, conjuntos e tuplas. O problema do exemplo é percorrer estruturas usadas em laboratório, estoque e medições.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para demonstrar iteracao sobre diferentes estruturas.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Percorra uma lista com for simples e com enumerate.
# 2. Percorra um dicionario com items, keys e values.
# 3. Percorra um conjunto com for.
# 4. Percorra uma tupla com for.
# 5. Percorra uma lista de tuplas usando desempacotamento.
#
# Casos especiais:
# 1. O foco e mostrar a forma idiomatica de iterar em cada estrutura.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Cada estrutura tem uma forma idiomatica de iteracao.
# O conceito importante aqui nao e apenas usar for, mas entender como cada tipo
# de dado costuma ser percorrido na pratica.
# --- Lista ---
etapas = ["coletar amostra", "medir vazao", "registrar resultado"]

print("Lista com for simples:")
for etapa in etapas:
    print(f"  {etapa}")

print("Lista com enumerate:")
# enumerate e util quando precisamos do indice junto com o valor.
# Em vez de controlar um contador manualmente, o proprio Python entrega os dois.
for indice, etapa in enumerate(etapas):
    print(f"  {indice}: {etapa}")

# --- Dicionario ---
estoque = {"parafuso": 20, "arruela": 35, "porca": 18}

print("Dicionario com items:")
# items percorre pares completos de chave e valor.
# Essa e a forma mais direta quando precisamos das duas partes do dicionario.
for material, quantidade in estoque.items():
    print(f"  {material}: {quantidade}")

print("Dicionario com keys:")
# keys percorre apenas as chaves do dicionario.
# Isso faz sentido quando queremos trabalhar so com os identificadores.
for material in estoque.keys():
    print(f"  {material}")

print("Dicionario com values:")
# values percorre apenas os valores.
# Isso e util quando o nome da chave nao importa para o calculo.
for quantidade in estoque.values():
    print(f"  {quantidade}")

# --- Conjunto ---
# Em conjuntos, a iteracao percorre elementos sem garantir ordem fixa.
# Isso acontece porque conjuntos nao foram feitos para preservar sequencia,
# mas para representar pertencimento e unicidade.
areas = {"laboratorio", "oficina", "almoxarifado"}

print("Conjunto com for:")
for area in areas:
    print(f"  {area}")

# --- Tupla ---
# Tuplas tambem podem ser percorridas com for como qualquer sequencia.
# A diferenca em relacao a lista nao esta na iteracao, mas no fato de serem fixas.
coordenadas = (12.0, 7.5, 2.0)

print("Tupla com for simples:")
for valor in coordenadas:
    print(f"  {valor}")

registros = [("sensor_1", 18.2), ("sensor_2", 19.1), ("sensor_3", 17.8)]

print("Lista de tuplas com desempacotamento:")
# Cada elemento da lista e uma tupla, entao o for pode desempacotar os campos.
# Isso e uma forma muito comum de iterar sobre registros compostos.
for sensor, temperatura in registros:
    print(f"  {sensor}: {temperatura}")

5.6 Cuidados ao modificar estruturas durante iteração

Remover ou adicionar elementos enquanto percorre uma estrutura pode causar comportamento inesperado. Em listas, elementos são pulados silenciosamente. Em dicionários, um RuntimeError é levantado.

A regra geral é: nunca modifique a estrutura que está sendo iterada diretamente. Em vez disso, use uma destas estratégias:

  • Construa uma nova estrutura com apenas os elementos desejados.
  • Itere sobre uma cópia (com list()) e modifique a original.
  • Colete as modificações e aplique depois do laço.

Exemplo Prático (filtro_lista_seguro.py): Introduz o problema de modificar listas durante a iteração e duas formas seguras de contornar isso. O problema do exemplo é filtrar leituras inválidas sem pular elementos.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para demonstrar filtragem segura em listas.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Comece com uma lista de leituras de sensor, incluindo valores invalidos.
# 2. Mostre o problema de remover elementos diretamente durante a iteracao.
# 3. Mostre uma solucao segura construindo uma nova lista.
# 4. Mostre uma segunda solucao segura iterando sobre uma copia.
#
# Casos especiais:
# 1. O foco e entender por que modificar a lista durante o for pode pular itens.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Leituras negativas representam dados invalidos neste exemplo.
# O objetivo e mostrar que alterar a lista durante o for pode baguncar a iteracao.
# Quando removemos um elemento, os demais mudam de posicao.
# Se o laco continua andando ao mesmo tempo, alguns elementos podem ser pulados.
leituras = [12, -1, -2, 15, -3, 18]

# --- Problema: remover durante a iteracao ---
copia_problema = list(leituras)
for leitura in copia_problema:
    if leitura < 0:
        copia_problema.remove(leitura)

print(f"Lista original: {leituras}")
print("Esperado: [12, 15, 18]")
print(f"Apos remocao insegura: {copia_problema}")

# --- Solucao 1: construir nova lista ---
# Em vez de alterar a lista atual, criamos outra com apenas os elementos desejados.
# Essa costuma ser a estrategia mais clara, porque separa a leitura da lista antiga
# da construcao da lista nova.
leituras_validas = []
for leitura in leituras:
    if leitura >= 0:
        leituras_validas.append(leitura)

print(f"Solucao 1 - nova lista: {leituras_validas}")

# --- Solucao 2: iterar sobre uma copia ---
# Outra estrategia segura e percorrer uma copia e modificar a original.
# Como a copia nao muda durante o for, a iteracao nao perde elementos.
copia_segura = list(leituras)
for leitura in list(copia_segura):
    if leitura < 0:
        copia_segura.remove(leitura)

print(f"Solucao 2 - iterando sobre copia: {copia_segura}")

Exemplo Prático (modificacao_dicionario_seguro.py): Introduz modificação segura de dicionários durante a iteração. O problema do exemplo é remover materiais com quantidade zero e depois registrar a reposição.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para modificar um dicionario de forma segura.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Comece com um dicionario de materiais e quantidades.
# 2. Remova os materiais com quantidade zero iterando sobre list(keys()).
# 3. Adicione materiais de reposicao em uma etapa separada.
# 4. Exiba o estado do dicionario apos cada fase.
#
# Casos especiais:
# 1. Modificar um dicionario durante iteracao direta causa erro em tempo de execucao.
# 2. Por isso, a remocao deve usar uma copia da lista de chaves.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# O dicionario inicial mistura materiais disponiveis e indisponiveis.
# Em dicionarios, modificar a estrutura durante a iteracao direta gera erro,
# porque o conjunto de chaves muda enquanto o Python ainda esta percorrendo esse conjunto.
estoque = {"parafuso": 20, "arruela": 0, "porca": 15, "bucha": 0, "rebite": 8}
print(f"Estoque inicial: {estoque}")

# --- Remocao segura ---
# list(estoque.keys()) cria uma copia das chaves.
# Assim, o for percorre uma lista separada, enquanto o dicionario original
# pode ser alterado com seguranca.
for material in list(estoque.keys()):
    if estoque[material] == 0:
        del estoque[material]
        print(f"Removido '{material}' por quantidade zero")

print(f"Estoque apos remocao: {estoque}")

# --- Adicao segura em etapa separada ---
# Aqui os novos itens sao preparados primeiro e adicionados depois com update.
# Essa ideia de "coletar antes e aplicar depois" e uma estrategia geral
# para evitar conflitos durante a iteracao.
reabastecimento = {}
materiais_novos = ["arruela", "bucha"]

for material in materiais_novos:
    reabastecimento[material] = 25

estoque.update(reabastecimento)
print(f"Estoque apos reposicao: {estoque}")

5.7 Qual estrutura escolher?

  • Use lista quando a ordem importa e você precisa alterar os dados.
  • Use dicionário quando precisa de acesso por chave.
  • Use conjunto quando quer evitar repetição e testar pertencimento.
  • Use tupla quando os dados são fixos e não devem ser alterados.

Exemplo Prático (seletor_estrutura.py): Introduz o critério de escolha entre lista, dicionário, conjunto e tupla. O problema do exemplo é associar cenários de engenharia à estrutura mais adequada.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Crie um programa para associar cenarios de engenharia a estruturas de dados.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina um dicionario em que cada chave seja um cenario.
# 2. Associe a cada cenario a estrutura de dados mais adequada.
# 3. Exiba o mapeamento final para estudo.
#
# Casos especiais:
# 1. Considere como criterios ordem, acesso por chave, repeticao e imutabilidade.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# O dicionario resume qual estrutura faz mais sentido em cada situacao.
# Ele tambem mostra outro uso de dicionario: associar um cenario a uma resposta.
# Aqui a estrutura de dados vira um mapa de recomendacoes.
recomendacoes = {
    "leituras na ordem em que chegam": "lista",
    "codigo de material para quantidade": "dicionario",
    "equipamentos sem repeticao": "conjunto",
    "coordenada fixa de um ponto": "tupla",
}

print("Estrutura recomendada por cenario:")
for cenario, estrutura in recomendacoes.items():
    print(f"- {cenario}: {estrutura}")

5.8 Passando estruturas de dados para funções

No capítulo de funções vimos que o parâmetro é uma variável local: ele só existe durante a execução da função, e alterar o parâmetro não altera a variável global de mesmo nome. Isso vale para números e textos.

Com estruturas de dados a história muda, e é aqui que muita gente se confunde. Quando passamos uma lista, um dicionário ou um conjunto como argumento, a função não recebe uma cópia: ela recebe a mesma estrutura. O parâmetro é apenas um segundo nome para a estrutura que já existia fora.

Pense na estrutura como uma caixa de peças na bancada e no nome da variável como uma etiqueta colada nela. Passar a caixa para a função é entregar uma segunda etiqueta para a mesma caixa. A partir daí, duas coisas bem diferentes podem acontecer:

  • Modificar o conteúdo (lista.append(...), dicionario[chave] = valor, conjunto.add(...), lista.remove(...)) mexe dentro da caixa. Como a caixa é a mesma, a mudança é visível fora da função — mesmo sem return.
  • Reatribuir o parâmetro (lista = [...]) apenas cola a etiqueta local em uma caixa nova. A caixa original continua intacta, e quem está fora não percebe nada.

Números, textos e tuplas são imutáveis: não existe “mexer no conteúdo” deles. Qualquer operação produz um valor novo, então uma função nunca consegue alterar o valor original que recebeu.

Esse comportamento é útil quando queremos justamente que a função atualize uma estrutura compartilhada, como um estoque. Mas ele também é a origem de bugs difíceis: uma função que “só deveria consultar” acaba alterando os dados do programa inteiro. Quando quiser evitar isso, escolha uma destas estratégias:

  • Monte uma estrutura nova dentro da função e devolva com return, sem tocar na recebida.
  • Passe uma cópia (list(...), dict(...), set(...)) para a função modificar à vontade.

Exemplos Práticos:

(funcao_modifica_lista.py): Introduz a diferença entre passar uma lista e passar um número para a função. O problema do exemplo é acrescentar um material à lista de bancada e tentar aumentar uma quantidade.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para observar o que acontece quando uma lista e
# passada para uma funcao.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma lista chamada materiais com os itens "cabo" e "sensor".
# 2. Defina uma variavel chamada quantidade com o valor 10.
# 3. Defina uma funcao chamada adicionar_material que receba uma lista e um
#    item, adicione o item na lista com append e exiba a lista.
# 4. Defina uma funcao chamada aumentar_quantidade que receba um numero,
#    some 5 a esse numero e exiba o resultado.
# 5. No script principal, exiba a lista, chame adicionar_material e exiba a
#    lista novamente.
# 6. Repita o mesmo roteiro com a variavel quantidade e a funcao
#    aumentar_quantidade.
#
# Casos especiais:
# 1. O objetivo e didatico: comparar uma estrutura de dados, que a funcao
#    consegue alterar, com um numero, que a funcao nao consegue alterar.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# A lista existe no script principal, fora de qualquer funcao.
materiais = ["cabo", "sensor"]

# A quantidade e um numero inteiro, ou seja, um valor simples.
quantidade = 10


# O parametro lista e um nome local, mas aponta para a mesma lista de fora.
# Por isso o append altera a lista original.
def adicionar_material(lista, item):
    lista.append(item)
    print(f"Dentro de adicionar_material: {lista}")


# O parametro numero tambem e um nome local, mas numeros nao podem ser
# alterados por dentro. A soma cria um novo valor, guardado so na funcao.
def aumentar_quantidade(numero):
    numero = numero + 5
    print(f"Dentro de aumentar_quantidade: {numero}")


# --- A lista e alterada pela funcao ---
print(f"Lista antes da chamada: {materiais}")
adicionar_material(materiais, "multimetro")
print(f"Lista depois da chamada: {materiais}")

# --- O numero nao e alterado pela funcao ---
print(f"Quantidade antes da chamada: {quantidade}")
aumentar_quantidade(quantidade)
print(f"Quantidade depois da chamada: {quantidade}")

(funcao_modifica_dicionario.py): Introduz funções que alteram um dicionário compartilhado sem usar return. O problema do exemplo é repor o estoque de um material e depois remover os itens esgotados.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para atualizar um estoque usando funcoes que
# recebem o dicionario como parametro.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina um dicionario chamado estoque com os pares "cabo": 4,
#    "sensor": 0 e "fusivel": 12.
# 2. Defina uma funcao chamada repor_material que receba o dicionario, o
#    nome do material e a quantidade, e some essa quantidade ao valor atual.
# 3. Defina uma funcao chamada remover_esgotados que receba o dicionario e
#    apague as chaves cujo valor seja zero.
# 4. No script principal, exiba o estoque, chame repor_material para o
#    material "cabo" com quantidade 6 e exiba o estoque novamente.
# 5. Chame remover_esgotados e exiba o estoque final.
#
# Casos especiais:
# 1. Nenhuma das funcoes usa return: elas alteram o proprio dicionario que
#    receberam, e a mudanca aparece no script principal.
# 2. Para apagar chaves com seguranca, percorra uma copia das chaves.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# O dicionario guarda a quantidade disponivel de cada material.
estoque = {"cabo": 4, "sensor": 0, "fusivel": 12}


# O parametro inventario aponta para o mesmo dicionario passado na chamada.
# Alterar uma chave aqui dentro altera o dicionario do script principal.
def repor_material(inventario, material, quantidade):
    inventario[material] = inventario[material] + quantidade


# Aqui tambem alteramos o dicionario recebido, agora apagando chaves.
# Percorremos uma copia das chaves com list() para nao modificar o
# dicionario enquanto ele esta sendo iterado.
def remover_esgotados(inventario):
    for material in list(inventario.keys()):
        if inventario[material] == 0:
            del inventario[material]


# Situacao inicial do estoque.
print(f"Estoque inicial: {estoque}")

# A funcao nao devolve nada, mas o estoque muda mesmo assim.
repor_material(estoque, "cabo", 6)
print(f"Depois da reposicao: {estoque}")

# A remocao das chaves esgotadas tambem aparece fora da funcao.
remover_esgotados(estoque)
print(f"Depois de remover esgotados: {estoque}")

(reatribuicao_vs_modificacao.py): Introduz a distinção central entre reatribuir o parâmetro e modificar a estrutura recebida. O problema do exemplo é zerar uma lista de leituras das duas formas e comparar o resultado.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para comparar duas formas de "zerar" uma lista
# dentro de uma funcao: reatribuindo o parametro ou modificando a lista.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma lista chamada leituras com os valores 10, 12 e 15.
# 2. Defina uma funcao chamada zerar_por_reatribuicao que receba uma lista,
#    atribua uma lista nova com o valor 0 ao parametro e exiba o parametro.
# 3. Defina uma funcao chamada zerar_por_modificacao que receba uma lista,
#    esvazie essa lista com clear, adicione o valor 0 com append e exiba a
#    lista.
# 4. No script principal, exiba a lista, chame zerar_por_reatribuicao e
#    exiba a lista novamente.
# 5. Em seguida chame zerar_por_modificacao e exiba a lista mais uma vez.
#
# Casos especiais:
# 1. Rastreie a saida antes de executar e escreva o que voce espera ver.
# 2. O objetivo e perceber que reatribuir o parametro so troca o nome local,
#    enquanto modificar a lista altera a estrutura compartilhada.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# A lista de leituras existe no script principal.
leituras = [10, 12, 15]


# Aqui o parametro lista passa a apontar para uma lista nova, criada dentro
# da funcao. O nome lista e local, entao a lista de fora continua intacta.
def zerar_por_reatribuicao(lista):
    lista = [0]
    print(f"Dentro de zerar_por_reatribuicao: {lista}")


# Aqui nenhuma lista nova e criada: mexemos no conteudo da lista recebida.
# Como e a mesma lista do script principal, a mudanca aparece la fora.
def zerar_por_modificacao(lista):
    lista.clear()
    lista.append(0)
    print(f"Dentro de zerar_por_modificacao: {lista}")


# Situacao inicial.
print(f"Leituras antes: {leituras}")

# --- Reatribuir o parametro: a lista de fora nao muda ---
zerar_por_reatribuicao(leituras)
print(f"Depois de zerar_por_reatribuicao: {leituras}")

# --- Modificar a lista: a lista de fora muda ---
zerar_por_modificacao(leituras)
print(f"Depois de zerar_por_modificacao: {leituras}")

(funcao_protege_original.py): Introduz as duas estratégias para preservar a estrutura original e mostra por que tuplas nunca são alteradas. O problema do exemplo é descartar leituras inválidas sem perder a lista completa.

# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
# Implemente um programa para preservar a estrutura original quando uma
# funcao precisa trabalhar sobre os dados.
#
# O programa nao precisa solicitar entradas do usuario.
#
# O programa deve funcionar da seguinte maneira:
# 1. Defina uma lista chamada leituras com os valores 12, -1, 15 e -3.
# 2. Defina uma tupla chamada limites com os valores 0 e 100.
# 3. Defina uma funcao chamada filtrar_validas que receba uma lista, monte
#    uma lista nova apenas com os valores maiores ou iguais a zero e
#    retorne essa lista nova.
# 4. Defina uma funcao chamada descartar_invalidas que receba uma lista e
#    remova dela os valores negativos.
# 5. Defina uma funcao chamada ampliar_limites que receba uma tupla e
#    retorne uma tupla nova com o segundo valor somado a 50.
# 6. No script principal, mostre que filtrar_validas nao altera a lista
#    original.
# 7. Chame descartar_invalidas passando uma copia da lista, feita com
#    list(), e mostre que a lista original continua igual.
# 8. Chame ampliar_limites e mostre que a tupla original continua igual.
#
# Casos especiais:
# 1. Sao duas estrategias para o mesmo objetivo: retornar uma estrutura
#    nova ou entregar uma copia para a funcao modificar.
# 2. Tuplas sao imutaveis, entao uma funcao nunca consegue alterar a tupla
#    recebida: qualquer mudanca produz uma tupla nova.
# -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-

# Leituras negativas representam medicoes invalidas neste exemplo.
leituras = [12, -1, 15, -3]

# A faixa de operacao do sensor e um registro fixo, entao usamos uma tupla.
limites = (0, 100)


# Estrategia 1: a funcao nao modifica nada. Ela monta uma lista nova e a
# devolve com return, deixando a lista recebida exatamente como estava.
def filtrar_validas(lista):
    validas = []
    for leitura in lista:
        if leitura >= 0:
            validas.append(leitura)
    return validas


# Esta funcao modifica a lista recebida, removendo os valores negativos.
# Quem chama e que decide se passa a lista original ou uma copia dela.
def descartar_invalidas(lista):
    for leitura in list(lista):
        if leitura < 0:
            lista.remove(leitura)


# Tuplas nao tem append nem remove: a unica saida e montar uma tupla nova.
def ampliar_limites(faixa):
    nova_faixa = (faixa[0], faixa[1] + 50)
    return nova_faixa


# --- Estrategia 1: retornar uma estrutura nova ---
leituras_validas = filtrar_validas(leituras)
print(f"Lista original: {leituras}")
print(f"Lista retornada: {leituras_validas}")

# --- Estrategia 2: entregar uma copia para a funcao modificar ---
copia = list(leituras)
descartar_invalidas(copia)
print(f"Copia modificada: {copia}")
print(f"Lista original preservada: {leituras}")

# --- Tuplas: a funcao nao consegue alterar a original ---
novos_limites = ampliar_limites(limites)
print(f"Tupla original: {limites}")
print(f"Tupla retornada: {novos_limites}")