Enquanto lê o texto, lembre-se de reproduzir os exemplos e realizar os exercícios sugeridos. Para esta unidade, você deve voltar ao capítulo 3 do tutorial, particularmente na subseção 3.1.2 que trata de listas. Depois, leia o capítulo 4, pelo menos até a seção 4.5.
Comecemos refazendo nosso algoritmo para calcular o valor de uma lista de compras, mas dessa vez vamos guardar os valores dos itens na memória do computador, ao invés de escrever no paper.
Escreva um programa que leia uma sequência de valores de itens de compra e mostre o valor da soma de todos os itens. O usuário deverá escrever o valor de cada item, um por linha. Quando não houver mais itens, o usuário irá indicar esse fato escrevendo um número negativo qualquer.
Para ter certeza de que entendemos o problema, primeiro escrevemos um exemplo de entrada:
3.50
5.00
16.36
-1
Assim, usando uma calculadora fica claro que devemos somar os valores das três primeiras linhas e obter o seguinte resultado.
24.86
Como de costume, vamos escrever primeiro um algoritmo em português.
lista_compras ← crie uma lista de compras
valor ← leia o valor de um item
enquanto valor > 0:
adicionar valor a lista_compras
valor ← leia um valor de item
soma ← 0
para cada valor na lista_compras:
soma ← soma + valor
mostrar o valor da soma total
Já sabemos implementar várias dessas linhas em Python, mas algumas têm instruções que não vimos. Já na primeira linha, topamos com a seguinte dificuldade: como criar uma lista de compras? Como você deve imaginar, não existe uma tal abstração lista de compras na linguagem de programação Python. Então para implementar esse programa, teremos que responder duas perguntas:
- como representar um item da lista de compras no contexto desse algoritmo?
- como armazenar um conjunto de itens da lista de compras em uma única variável?
A resposta da primeira pergunta reforça que o que armazenamos de fato na memória do computador não são itens de compra, mas dados relacionados a um item. Nesse caso, o único dado de interesse é o valor desse item, que devemos representar como um número de ponto flutuante. Assim, para responder a segunda pergunta precisamos de um mecanismo para armazenar um conjunto de números de ponto flutuante. Em Python, a maneira natural de armazenar uma coleção de dados é criando uma lista. Uma implementação do algoritmo seria a seguinte:
# leia uma sequência de valores de itens
lista_compras = []
valor = float(input())
while valor >= 0:
lista_compras.append(valor)
valor = float(input())
# somar todos os valores da lista
soma = 0.0
for valor in lista_compras:
soma += valor
print(soma)
Há diversas novidades nesse trecho de código. Vamos explorá-lo em partes.
Listas
A expressão []
cria uma nova variável do tipo lista que inicialmente
está vazia. Há várias maneiras de criar uma lista. Vejamos algumas.
Experimente utilizando o modo interativo do interpretador:
>>> lista_vazia = []
>>> outra_lista_vazia = list()
>>> primos = [2, 3, 5, 7, 11]
>>> cinco_zeros = [0] * 5
>>> escritores = ["Vinicius de Moraes",
... "Cecília Meireles",
... "Mary Shelley",
... "Cora Coralina",
... "Pedro dos Anjos",]
>>> escritoras = escritores[1:4]
>>> notas = [10.0, 7.5, 3.14]
>>> palavras = " Ando por aí querendo te encontrar".split()
O número de referências que estão armazenadas em uma lista pode ser
variável. Ela pode começar vazia ou com alguns elementos. Podemos
inserir um elemento no final da lista com a operação append
e
remover um elemento do final da lista com a operação pop
.
>>> primos = [2, 3, 5, 7, 11]
>>> type(primos)
<class 'list'>
>>> len(primos)
5
>>> primos.append(17)
>>> primos
[2, 3, 5, 7, 11, 17]
>>> primos.extend([19, 23])
>>> primos
[2, 3, 5, 7, 11, 17, 19, 23]
>>> primos.pop()
23
>>> primos
[2, 3, 5, 7, 11, 17, 19]
>>> primos = primos + [29, 31]
>>> primos
[2, 3, 5, 7, 11, 17, 19, 29, 31]
Procure na documentação outras funções para remover ou inserir em uma posição arbitrária e para remover um determinado elemento. Experimente tentar remover elementos de listas vazias ou remover elementos que não existem na lista.
Uma lista é uma variável que contém um conjunto de referências
para outras variáveis. Você pode desenhar a variável lista_compras
como um grande retângulo na memória que contém referências para
diversas variáveis. Ao executar o programa com o exemplo de entrada,
obteremos uma figura parecida com a seguinte:
Você pode imaginar que na verdade há diversos nomes distintos referenciando a variáveis distintas, como na figura:
Os motivos por que usamos uma lista ao invés de diversas variáveis soltas são:
- podemos armazenar um número variável de elementos na memória, todos representados por um mesmo nome;
- podemos acessar uma variável distinta dessa coleção por meio de um índice não constante.
Isso é importante porque, no momento em que estamos escrevendo um algoritmo, não sabemos quantos elementos a coleção deverá ter, nem qual posição será acessada. Vamos estudar o programa seguinte.
n = input("Digite quantos amigos você tem? ")
amigos = []
i = 0
while i < n:
nome = input(f"Digite o nome do amigo número {i}: ")
amigos.append(nome)
i += 1
j = int(input("Digite um número: "))
print(f"Seu amigo número {j} chama-se {amigos[j]}")
Experimente executar esse programa. O número de amigos só será
conhecido em tempo de execução, assim o tamanho da lista irá variar de
execução para execução, bem como o índice j
do amigo consultado.
Observe que nada garante que o número armazenado na variável j
corresponde a um número de amigo válido. O que acontece quando você
digita um número negativo? E quando digita um número maior ou igual o
número n
?
Assim como as variáveis simples, também podemos mudar os valores a que se referem os elementos de uma lista. O seguinte programa multiplica cada elemento de uma lista de inteiros por um número diferente.
sequencia = [1] * 5
i = 1
while i < 10:
sequencia[i] = sequencia[i - 1] * i
i += 1
print(sequencia)
Um parêntese: debugger
Você consegue dizer o que o programa acima faz? Qual a saída desse programa? Para responder, temos que simular esse algoritmos usando lápis e papel. Quando você tiver mais experiência e estiver testando programas maiores, precisará usar uma ferramenta para auxiliar a simular algoritmos. Esse tipo de ferramenta chama-se depurador ou debugger.
Depois de ter simulado no papel, você pode tentar depurar esse trecho.
Para isso, você precisa configurar uma IDE ou um editor de texto com
suporte a debugger em Python (como o VSCode). Alternativamente, você
pode utilizar o debugger de Python padrão no sistema. Guarde o
programa seguinte como um arquivo sequencia.py
,
sequencia = [1] * 5
i = 1
while i < 10:
breakpoint()
sequencia[i] = sequencia[i - 1] * i
i += 1
print(sequencia)
e execute em um terminal usando python3 sequencia.py
. Isso irá
iniciar um sessão do Python debugger padrão (pdb) toda vez que a linha
que contém breakpoint()
for executada. Nessa sessão, você pode
inspecionar os valores atuais das variáveis, assim como num terminal
interativo. Depois de ver o valor das variáveis, digite continue
,
para continuar executando a próxima linha até a próxima instrução
breakpoint()
.
Listas heterogêneas
Na grande maioria da vezes, vamos considerar apenas listas que contêm elementos de um determinado tipo. As listas em Python, no entanto, permitem listas que contêm elementos de tipos heterogêneos. Vejamos um exemplo:
>>> numeros = ["um", 2, 3.0]
>>> type(numeros[0])
<class 'str'>
>>> type(numeros[1])
<class 'int'>
>>> type(numeros[2])
<class 'float'>
Enquanto isso pode ser conveniente às vezes, você não deve criar
listas desse tipo, pelo menos agora. Um dos motivos é que não podemos
tratar os elementos dessa lista de maneira uniforme. Qual seria o
resultado de numeros[0] + numeros[1]
?
Percorrendo listas
Voltando ao exemplo da lista de compras, agora precisamos percorrer os
elementos da lista. Para isso, usamos a construção for
como
seguinte:
# somar todos os valores da lista
soma = 0.0
for valor in lista_compras:
soma += valor
print(soma)
Você pode ler o trecho acima como para cada valor
de
lista_compras
, execute as seguintes instruções. O que esse trecho
faz é associar um nome valor
a um elemento da lista e executar corpo
de comandos do for
, uma vez para cada elemento da lista e em ordem.
Em cada iteração, valor
estará referenciando um elemento da lista.
Observe que embora a variável soma
não faça parte da construção do
for
, ela é um elemento importante para entendermos esse laço. Ao
final de cada iteração do for
, o valor dessa variável será a soma
parcial dos valores até o item considerado atualmente. Por esse
motivo, ela é chamada de variável acumuladora. Faça os exercícios
de fixação para descobrir mais usos de variáveis acumuladoras.
Na verdade, você pode usar o for
para percorrer qualquer iterador em
Python. Não vamos estudar iteradores em detalhes, aqui basta dizer que
eles são objetos que se comportam como uma sequência de itens. Um
iterador bastante comum é obtido pela função range
, que representa
um intervalo inteiro. Podemos usá-lo da seguinte forma:
for i in range(10):
print(f"Executando iteração com i = {i}")
Isso irá imprimir a saída
Executando iteração com i = 0
Executando iteração com i = 1
Executando iteração com i = 2
Executando iteração com i = 3
Executando iteração com i = 4
Executando iteração com i = 5
Executando iteração com i = 6
Executando iteração com i = 7
Executando iteração com i = 8
Executando iteração com i = 9
Procure a documentação de range
para ver outras formas de usá-la.
Tente descobrir o que faz o programa seguinte. Tente primeiro usando
apenas lápis e papel por pelo menos alguns minutos e depois execute
com um computador. Uma dica: a instrução print(" ", end="")
imprime
um espaço, mas não insere a quebra a linha depois.
for i in range(5, 11):
for j in range(10 - i):
print(" ", end="")
for j in range(2 * i):
print("*", end="")
for j in range(10 - i):
print(" ", end="")
print()
Enquanto o valor devolvido por range
funciona como uma lista, ele
não é uma lista. Mas se quiser, você pode converter um intervalo (ou
qualquer sequência) em uma lista. Na maioria das vezes, isso não é
necessário, mas você pode querer verificar:
>>> intervalo_pares = range(2, 11, 2)
>>> intervalo_pares
range(2, 11, 2)
>>> type(intervalo_pares)
<class 'range'>
>>> pares = list(intervalo_pares)
>>> pares
[2, 4, 6, 8, 10]
>>> minha_string = "Uma string"
>>> type(minha_string)
<class 'str'>
>>> sequencia_caracteres = list(minha_string)
>>> sequencia_caracteres
['U', 'm', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g']
>>> type(sequencia_caracteres)
<class 'list'>
Um outro exemplo
Você pode se questionar porque precisamos guardar todos os valores da nossa lista de compras se apenas gostaríamos de somá-los. De fato, não precisamos: nesse exemplo, ter criado uma lista apenas fez com que utilizássemos mais memória (para armazenar uma lista) do que era necessário. Nesta disciplina, as entradas não serão tão grandes a ponto de precisarmos economizar memória, então sempre que for conveniente, vamos armazenar os dados em uma lista. Há algumas vantagens de escrever programas assim: primeiro, é mais fácil pensar sobre uma lista e, segundo, há uma série de funções prontas para tratar listas em Python. O último trecho de código poderia ser substituído pela seguinte linha
print(sum(lista_compras))
Pesquise sobre a função sum
e outras funções agregadoras de Python,
mas tenha em mente que nesta disciplina queremos aprender e exercitar
os algoritmos explicitamente.
Um exercício em que é necessário manter uma lista em memória é o seguinte:
Escreva um programa que receba uma sequência de nomes digitados no teclado e imprima as iniciais. Para sinalizar que não há mais nomes, o usuário irá digitar um traço
-
.
Um exemplo de entrada é
Maria
João
Pedro
Catarina
Carlos
-
Com o que já aprendemos, podemos escrever o seguinte:
# ler uma sequência de nomes
lista_nomes = []
nome = input()
while nome != "-":
lista_nomes.append(nome)
nome = input()
# guardar as iniciais
lista_iniciais = []
for nome in lista_nomes:
inicial = nome[0]
lista_iniciais.append(inicial)
print(" ".join(lista_iniciais))
Preste atenção no argumento de print
e pesquise sobre a função
join
, que nos auxilia a criar uma string separada por espaços. Ao
testarmos esse programa e analisarmos a saída, no entanto, iremos
notar um problema.
M J P C C
A saída contém iniciais repetidas. Isso é fácil de corrigir quando guardamos a lista de iniciais: basta testar se já encontramos a inicial antes de inserir. Podemos fazer isso usando um operador novo.
# ler uma sequência de nomes
lista_nomes = []
nome = input()
while nome != "-":
lista_nomes.append(nome)
nome = input()
# guardar as iniciais
lista_iniciais = []
for nome in lista_nomes:
inicial = nome[0]
if inicial not in lista_iniciais:
lista_iniciais.append(inicial)
print(" ".join(lista_iniciais))
O operador in
(ou sua versão negada not in
) devolve um valor
booleano indicando se o item à esquerda está na lista à direita.
Copiando uma lista e referências
Você deve ter reparado que sempre que falamos do operador de
atribuição =
, nós distinguimos entre o nome da variável e o valor da
variável. Isso é particularmente importante quando trabalhamos com
listas. Por exemplo, tente descobrir o que é impresso pelo seguinte
trecho:
mamiferos = ["golfinho", "humano", "cachorro"]
animais = mamiferos
animais.append("sapo")
print(mamiferos)
Ao executar esse código você verá que não teremos impresso apenas
espécies mamíferas, mas também um anfíbio. Muito embora uma tradução
ingênua para o português poderia dizer que “sapo” foi adicionado
apenas a lista de animais, na verdade a lista animais
e mamiferos
são uma só! Uma representação em memória desse trecho é
Quando escrevemos a linha animais = mamiferos
o que fazemos
é dar um novo nome à mesma lista que foi criada antes.
Para fazer uma cópia de uma lista, precisamos de um pouco mais de trabalho.
Observe e procure entender o código abaixo
mamiferos = ["golfinho", "humano", "cachorro"]
mammals = mamiferos
animais = []
for m in mamiferos:
animais.append(m)
animais.append("sapo")
mammals[2] = "elefante"
print(mamiferos)
print(mammals)
print(animais)
Agora, a saída será:
['golfinho', 'humano', 'elefante']
['golfinho', 'humano', 'elefante']
['golfinho', 'humano', 'cachorro', 'sapo']
Uma representação da memória poderia ser:
List comprehension
Criar uma lista a partir de outra sequência é tão comum que Python tem uma maneira mais curta de escrever o mesmo código, que é chamada de list comprehension. Por exemplo, podemos criar uma cópia de uma lista de números, mas multiplicando por dois.
notas = [3.5, 6.0, 1.9, 10, 7.4, 4.3]
dobros = [2 * nota for nota in notas]
print(dobros)
Podemos, inclusive, filtrar um subconjunto de números:
notas = [3.5, 6.0, 1.9, 10, 7.4, 4.3]
dobros_notas_vermelhas = [2 * nota for nota in notas if nota < 5]
print(dobros_notas_vermelhas)
Estude e experimente trabalhar com list comprehensions. No entanto, por enquanto, prefira as versões mais explícitas apresentadas anteriormente quando for resolver os exercícios e tarefas desta disciplina.
Saindo antecipadamente
Vimos que um for
executa uma iteração para todo elemento da
sequência. Vamos ver mais um exemplo:
Escreva um programa que imprima todos os divisores não triviais de um número (isso é, todos os divisores que não são um ou o próprio número).
Como sempre, queremos escrever um algoritmo para esse problema em português.
n ← leia um número do teclado
divisores ← crie uma lista vazia
para d de 2 até n - 1:
se d divide n:
adicione d aos divisores
devolva divisores
Nesse ponto, deve ser trivial traduzir esse algoritmo em um código em Python:
n = int(input())
divisores = []
for d in range(2, n):
if n % d == 0:
divisores.append(d)
print(divisores)
Um número é primo se ele é maior do que um e não tém divisores não triviais. É fácil modificar o código acima e verificar se um número é primo:
n = int(input())
divisores = []
for d in range(2, n):
if n % d == 0:
divisores.append(d)
if n == 1 or divisores:
print("O número é 1 ou tem divisores não triviais")
else:
print("O número é primo")
Para entender esse código precisamos perceber uma sutileza:
divisores
é uma lista, mas ela foi usada no lugar em que
esperaríamos um valor booleano. Em Python, coleções (como listas)
podem ser usadas como valores de verdade: elas são consideradas True
sempre que não forem vazias, e False
caso contrário. Pesquise sobre
as várias formas de
testar valores de verdade
em Python 3.
Se você é impaciente deve estar incomodado com o código acima: ele
executa mais operações do que é necessário. Parece latente que um
número como 1000000000
não é primo. Ainda assim, se executarmos o
código acima e digitarmos esse valor, teremos uma surpresa
desagradável — e não interessa que você tenha um computador top de
linha ou mesmo um supercomputador!
O motivo é que desde o momento em que testamos o primeiro divisor, já
sabíamos que o número 1000000000
não era primo, mas o programa é
alheio ao seu sofrimento e continua obstinado em executar todas as
iterações. Para terminar um laço antes do final, usamos um comando
especial break
. Isso irá terminar o laço e continuar na instrução
imediatamente posterior.
n = int(input())
divisor_encontrado = False
for d in range(2, n):
if n % d == 0:
divisor_encontrado = True
break
if n == 1 or divisor_encontrado:
print("O número é 1 ou tem divisores não triviais")
else:
print("O número é primo")
O comando for
permite um comando else
opcional, cujo bloco de
comandos é executado sempre que o laço executa todas iterações
completamente. Essa é uma peculiaridade de Python (não há muitas
outras linguagens com esse tipo de construção) e você deve evitá-la
até ter mais experiência. Poderíamos reescrever o código assim:
n = int(input())
for d in range(2, n):
if n % d == 0:
divisor_encontrado = True
break
else:
divisor_encontrado = False
if n == 1 or divisor_encontrado:
print("O número é 1 ou tem divisores não triviais")
else:
print("O número é primo")
Construindo um menu de opções
Vejamos um outro exemplo em que utilizar um break
pode facilita a
vida do programador. Resolva o seguinte exercício:
Escreva uma caculadora que realiza soma e subtração. Uma instrução começa com o operador e uma linha seguido de duas linhas com os operandos. O programa deve executar quantas operações forem fornecidas pelo usuário, que digitará
F
quando quiser terminar.
Aqui está um exemplo de entrada:
+
3
5
-
4
1
-
5
0
F
e a saída correspondente:
8
3
5
Tente escrever um algoritmo e um código em Python para esse exercício. Depois de ter feito a sua versão, estude como eu escreveria:
while True:
operador = input()
if operador == "+":
num1 = float(input())
num2 = float(input())
soma = num1 + num2
print(soma)
elif operador == "-":
num1 = float(input())
num2 = float(input())
diferenca = num1 - num2
print(diferenca)
elif operador == "F":
break
else:
print("Operação inválida")
Experimente adicionar outras operações a sua calculadora.