Instruções: Enquanto você lê, você deve acompanhar os exemplos utilizando um console e Python. Para essa unidade, você deve ler o restante do capítulo 3 a partir de 3.1.2 e as seções do capítulo 4 até 4.5 do tutorial Python.
Vamos refazer nosso algoritmo para a lista de compras, mas dessa vez vamos guardar os valores dos itens na memória do computador. Vamos resolver o seguinte exercício:
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 um valor de 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 todas as linhas, com exceção da primeira: criar uma lista de compras: 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. Vamos experimentar
algumas, experimente:
>>> 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]
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 lista_compras
como grande
quadrado 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 as 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 experimentar o seguinte programa:
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. 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 por um número diferente cada elemento de uma lista de inteiros:
sequencia = [1] * 5 i = 1 while i < 10: sequencia[i] = sequencia[i - 1] * i i += 1 print(sequencia)
Um parêntese
Você consegue dizer o que esse programa faz? Tente simular no papel e
depois verifique executando esse programa com o auxílio de um debugger
.
Para isso, você pode utilizar uma IDE configurada apropriadamente
como o VSCode. Alternativamente, salve o programa
seguinte como 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 por enquanto. 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 como _para cada valor
de lista*compras*, execute as seguinte instruções_. O que esse trecho faz é criar um novo nome de variável
valore executar bloco de código indentado do
foruma vez para cada elemento da lista, em ordem. Em cada iteração,
valor`
estará referenciando um elemento da lista.
Observe que embora não faça parte do for
, a variável soma
está
associada a esse laço. Ao final de cada iteração ela terá o valor da
soma parcial dos valores até o item corrente, por isso é 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 ou
sequência em Python. Não vamos estudar iteradores em detalhes. 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 seguinte 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 seguinte programa:
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 pode converter um intervalo (ou
qualquer sequência) em uma lista. Isso na maioria das vezes não é
necessário, mas você pode querer verificar isso:
>>> 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. Nessa 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 nessa nessa disciplina queremos aprender e
exercitar os algoritmos explicitamente.
Um exercícios 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 lista. 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)
Estudo 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 as tarefas.
Saindo de um laço 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 de um número não triviais (isso é, 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
(Truth Value Testing)
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. 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
Vamos ver outro exemplo em que utilizar um break
pode
facilitar escrever um programa. Vamos resolver 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 digitá
F
quando quiser terminar.
Aqio 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 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.