Condições e ciclos
Comparações, decisões, for, while, acumuladores, limites e terminação.
Comparar e combinar condições
Com nota = 10, a condição nota >= 10 é verdadeira, enquanto com nota = 9 é falsa; a condição escolhe instruções e o ciclo repete-as, podendo alterar o estado, isto é, os valores associados aos nomes, em cada iteração.
==, !=, <, <=, > e >= comparam valores. Para verificar se uma nota está entre 0 e 20, incluindo os extremos, podes escrever 0 <= nota <= 20. Não uses is para comparar números ou texto: is pergunta se tens o mesmo objeto.
not p nega uma condição; p and q exige ambas; p or q exige pelo menos uma. A ordem de precedência é not, and, or, mas parênteses tornam a intenção explícita.
Numa condição, False, None, zero e coleções vazias contam como falso. Os restantes valores destes tipos contam como verdadeiro. Por exemplo, if texto: testa se uma string não está vazia; não testa se contém a palavra "True".
Python avalia and e or com curto-circuito. Em divisor != 0 and total / divisor > 2, a divisão só ocorre quando o divisor é diferente de zero.
Estes operadores devolvem um dos operandos, não necessariamente bool: "" or "sem nome" vale "sem nome". Nas condições iniciais, usa comparações que produzam True ou False.
Escolher um ramo
Uma cadeia if / elif / else escolhe no máximo um ramo, o primeiro cuja condição é verdadeira. Dois if separados podem executar ambos. A indentação define o corpo; usa quatro espaços de forma consistente.
nota = int(input())
if not 0 <= nota <= 20:
print("inválida")
elif nota >= 10:
print("aprovado")
else:
print("reprovado")Executa o código para ver a saída e os gráficos.
A entrada 10 passa a validação e escolhe o segundo ramo, por isso o programa escreve aprovado. Testa 9, 10, 20 e 21 para verificar as fronteiras, onde um > no lugar de >= costuma mudar o resultado.
Ciclos for e a função range
for percorre os elementos de um iterável, um objeto que fornece valores um a um.
range(inicio, fim, passo) fornece inteiros até ao limite fim, que fica excluído. Omitir o início usa 0; omitir o passo usa 1. O passo não pode ser zero.
| Chamada | Produz | Porquê |
|---|---|---|
range(1, 5) | 1, 2, 3, 4 | Conta de 1 até antes de 5 |
range(5, 0, -2) | 5, 3, 1 | Desce de 2 em 2 sem atingir 0 |
range(5, 0) | Vazio | Passo 1 com início acima do fim |
sum soma os valores de um iterável.
Para somar os quadrados dos inteiros de 1 até n, inicializamos um acumulador com zero e acrescentamos um termo em cada iteração. Para estes números, soma += k * k abrevia soma = soma + k * k: calcula o novo total e volta a associá-lo a soma.
n = int(input())
soma = 0
for k in range(1, n + 1):
soma += k * k
print(soma)Executa o código para ver a saída e os gráficos.
k | soma antes | Termo acrescentado | soma depois |
|---|---|---|---|
| 1 | 0 | 1 | 1 |
| 2 | 1 | 4 | 5 |
| 3 | 5 | 9 | 14 |
| 4 | 14 | 16 | 30 |
Depois de tratar k, a soma contém os quadrados de 1 até k. Esta propriedade é um invariante do ciclo e explica o resultado. Se n é zero, não há iterações e a soma vazia vale zero. Para um produto, o valor inicial costuma ser 1, porque multiplicar por zero destruiria o resultado.
Ciclos while e terminação
while testa a condição antes de cada iteração. Pode executar zero vezes. Usa-o quando o fim depende dos dados ou de uma condição que muda durante o cálculo.
n = int(input())
soma = 0
while n > 0:
soma += n % 10
n //= 10
print(soma)Executa o código para ver a saída e os gráficos.
n % 10 extrai o último dígito e n // 10 elimina-o; n //= 10 guarda esse quociente em n para avançar para o prefixo seguinte. A tabela acompanha a entrada 3083 passo a passo:
n antes | Dígito n % 10 | soma depois | n depois |
|---|---|---|---|
| 3083 | 3 | 3 | 308 |
| 308 | 8 | 11 | 30 |
| 30 | 0 | 11 | 3 |
| 3 | 3 | 14 | 0 |
O dígito zero mantém a soma, mas não termina o ciclo: ainda resta o prefixo 3. A paragem depende de n == 0, depois de retirar todos os dígitos.
Para n > 0, o quociente inteiro por 10 é menor que n e continua não negativo, por isso chega a zero. Sem a atualização de n, a condição continuaria verdadeira.
Este algoritmo assume entrada não negativa. Para aceitar inteiros negativos, usa n = abs(n) antes do ciclo. Para a entrada zero, a soma permanece zero, que é o resultado pretendido.
Construir um número a partir dos dígitos
Para inverter um inteiro não negativo sem o converter em texto, retiramos os
seus dígitos da direita para a esquerda e colocamo-los no fim de outro inteiro.
Em resultado * 10 + digito, multiplicar por 10 abre a posição das unidades;
a adição ocupa essa posição com o dígito seguinte.
n = 1204
resultado = 0
while n > 0:
resultado = resultado * 10 + n % 10
n //= 10
print(resultado)
A saída é 4021. O estado permite conferir onde fica o zero:
| Prefixo antes | Dígito | Resultado depois |
|---|---|---|
| 1204 | 4 | 4 |
| 120 | 0 | 40 |
| 12 | 2 | 402 |
| 1 | 1 | 4021 |
Para 1200, o resultado é 21: zeros à esquerda não fazem parte da
representação de um inteiro. Se o contrato exigir conservar quatro posições,
precisamos de texto ou de guardar também o comprimento. Para zero, o ciclo
não executa e o resultado inicial zero já é correto.
A divisão com resto também permite mudar de base. Para escrever 13 em base 2,
as divisões sucessivas produzem os restos 1, 0, 1 e 1. O primeiro resto é o
dígito menos significativo, por isso a representação lê-se na ordem inversa,
1101. Conferimos com .
Em base b, usamos n % b e n // b, exigindo um inteiro b >= 2.
Sentinelas, break, continue e else
Uma sentinela marca o fim dos dados e não deve ser tratada como uma entrada normal. Se zero termina uma sequência de medições positivas, testa-o antes de o acrescentar à soma.
soma = 0
n = int(input())
while n != 0:
if n > 0:
soma += n
n = int(input())
print(soma)
Com entradas 3, 5 e 0, a soma passa por 0, 3 e 8; o zero termina o ciclo sem ser somado. Sem a leitura nova no fim do corpo, o ciclo repetiria a primeira medição.
break sai do ciclo mais interno. continue passa à próxima iteração; num while, confirma que não salta a atualização necessária à terminação. O else de um ciclo corre quando o percurso termina sem break, mesmo que não tenha havido iterações.
for divisor in range(2, 7):
if 7 % divisor == 0:
print("composto")
break
else:
print("primo")
O exemplo escreve primo, porque nenhum divisor interrompe o ciclo, e o else está alinhado com for, não com if. Ao alterar 7, altera também o limite se pretenderes testar todos os divisores próprios; a lição de funções separa esse cálculo da entrada.
Escolher o percurso
Percorre valores diretamente quando só precisas deles; usa índices quando precisas de uma posição e enumerate quando precisas de ambos. Num ciclo aninhado, o corpo interior executa para cada valor do exterior.
for linha in range(2):
for coluna in range(3):
print(linha, coluna)
Os pares visitados são (0, 0), (0, 1), (0, 2), (1, 0), (1, 1) e (1, 2): seis, não cinco. Se há três linhas e quatro colunas, visitas 12 pares, não sete.
Exercícios
Diagnosticar a ordem dos ramos
Prevê a saída para nota = 16. Corrige a ordem para escrever bom nas notas de 15 a 20 e aprovado nas de 10 a 14. Assume uma nota válida de 0 a 20. Explica o que aconteceria se substituísses elif por outro if.
if nota >= 10:
print("aprovado")
elif nota >= 15:
print("bom")
else:
print("reprovado")Pista
Solução
Para 16, o primeiro teste é verdadeiro e a saída é aprovado; o elif nem é avaliado. A correção testa primeiro nota >= 15, depois nota >= 10. Para 9, ambos falham e corre o else; para 10, só o segundo ramo é escolhido; para 15, o primeiro.
Com dois if independentes na versão original, 16 satisfaz ambos e escreve aprovado e bom. Nesse código, o else pertence ao segundo if, por isso 10 escreveria aprovado e depois reprovado.
Confere a tua resposta:
- Previ aprovado para 16.
- Pus o teste mais restrito primeiro.
- Distingui uma cadeia de dois if independentes.
Evitar uma divisão inválida
Para d = 0, d != 0 and 10 / d > 2 lança uma
divisão por zero?
Pista
Mais uma pista
O primeiro operando vale False.
Solução
Não. O resultado é False e a divisão não executa. Inverter a
ordem dos operandos perderia esta proteção.
- Sim, dá ZeroDivisionError. A divisão não chega a ser avaliada.
- Não, dá
True. O primeiro operando é falso, logo a conjunção é falsa. - Não, dá
False. and termina quando o primeiro operando é falso.
Erros frequentes
Seguir um range
Quanto vale sum(range(7, 0, -2))?
Pista
Mais uma pista
Solução
Erros frequentes
Distinguir dígito zero de prefixo zero
Segue este programa para n = 4072: indica o dígito extraído, o prefixo restante e a soma em cada iteração. Um colega propõe parar quando n % 10 == 0. Que resultado obteria e por que está errado?
soma = 0
while n > 0:
soma += n % 10
n //= 10
print(soma)Pista
Solução
n antes | Dígito | Prefixo depois | Soma depois |
|---|---|---|---|
| 4072 | 2 | 407 | 2 |
| 407 | 7 | 40 | 9 |
| 40 | 0 | 4 | 9 |
| 4 | 4 | 0 | 13 |
O ciclo correto termina no prefixo zero e escreve 13. Parar no dígito zero terminaria em 40 com soma 9, perdendo o 4. A medida de progresso é o inteiro não negativo n, que diminui por divisão inteira em cada iteração.
Confere a tua resposta:
- Segui os prefixos 4072, 407, 40, 4 e 0.
- Obtive soma 13.
- Expliquei por que parar num dígito zero dá 9.
Distinguir encontrar de esgotar a procura
Prevê a saída para valores = [2, -1, 4], depois [2, 0, 4] e finalmente
[]. Um colega substitui break por continue. Que saída terá agora a
primeira lista e por que já não resolve a mesma procura?
for valor in valores:
if valor < 0:
print("encontrado")
break
else:
print("nenhum")Solução
Com [2, -1, 4], escreve encontrado e sai por break, sem executar o
else. Com [2, 0, 4], percorre tudo sem negativo e escreve nenhum.
Com [], também esgota o percurso sem break e escreve nenhum.
Substituindo por continue, o -1 ainda escreve encontrado, mas só salta o
resto dessa iteração. O percurso continua com 4 e acaba normalmente,
executando else: escreve também nenhum. O else não pergunta se houve
iterações ou se a condição do if alguma vez foi verdadeira; pergunta se
o ciclo terminou normalmente, sem break.
Confere a tua resposta:
- Previ encontrado só quando existe um negativo.
- Previ nenhum tanto sem negativos como na lista vazia.
- Expliquei que break impede else.
Somar com limites inclusivos
Lê um inteiro n não negativo e escreve a soma dos múltiplos positivos de 3 entre 1 e n, incluindo n se for múltiplo de 3. Para n = 10, somas 3 + 6 + 9 = 18. Usa um ciclo para praticar o acumulador.
n = int(input())
total = 0
print(total)Verifica o código para comparar o resultado com os testes.
Testes
Sem termos
Entrada0
Esperado:0
Antes do primeiro múltiplo
Entrada2
Esperado:0
Limite incluído
Entrada3
Esperado:3
Vários termos
Entrada10
Esperado:18
Outro limite incluído
Entrada12
Esperado:30
0
0
2
0
3
3
10
18
12
30
Pista
Começa a soma em zero. O último valor permitido é n.
Mais uma pista
Podes percorrer range(3, n + 1, 3), ou percorrer todos os inteiros e testar
o resto.
Solução
O passo 3 visita só os múltiplos. O limite n + 1 permite incluir n. Quando n é menor que 3, o percurso é vazio e o total permanece zero.
n = int(input())
total = 0
for valor in range(3, n + 1, 3):
total += valor
print(total)Erros frequentes
Excluir n com range(3, n, 3), imprimir dentro do ciclo ou esquecer a
inicialização.
Somar até à sentinela
Define soma_ate_zero(valores): soma todos os valores até ao primeiro zero, incluindo negativos, sem incluir a sentinela nem o que vem depois. Ao contrário do exemplo da lição, que filtra só positivos, aqui cada valor antes do zero conta.
def soma_ate_zero(valores):
return 0Verifica o código para comparar o resultado com os testes.
Testes
Dois valores
Código do testeprint(soma_ate_zero([3, 5, 0, 9]))Esperado:8
Zero imediato
Código do testeprint(soma_ate_zero([0]))Esperado:0
Sem resto
Código do testeprint(soma_ate_zero([4, 0]))Esperado:4
Negativos incluídos
Código do testeprint(soma_ate_zero([3, -2, 0, 9]))Esperado:1
Só negativos
Código do testeprint(soma_ate_zero([-4, -1, 0]))Esperado:-5
print(soma_ate_zero([3, 5, 0, 9]))8
print(soma_ate_zero([0]))0
print(soma_ate_zero([4, 0]))4
print(soma_ate_zero([3, -2, 0, 9]))1
print(soma_ate_zero([-4, -1, 0]))-5
Pista
Para no zero antes de somar.
Mais uma pista
Um filtro if valor > 0 copiado da lição chumba o teste com negativos: [3, -2, 0] deve dar 1, não 3. Soma cada valor antes do zero sem filtrar.
Solução
Percorre a lista e interrompe no zero. Para [3, 5, 0, 9], a soma passa por 0, 3 e 8. Para [3, -2, 0, 9], passa por 0, 3 e 1.
def soma_ate_zero(valores):
soma = 0
for valor in valores:
if valor == 0:
break
soma += valor
return somaErros frequentes
Somar o zero da sentinela; continuar a somar depois do zero; filtrar só positivos e ignorar negativos.
Construir uma representação noutra base
Define representar(n, base): recebe um inteiro não negativo e uma base inteira
de 2 a 10, incluindo os extremos. Devolve uma string com a representação de n
nessa base, sem zeros à esquerda, exceto o próprio zero, que se representa por
"0". Usa divisões sucessivas com resto. Justifica por que a ordem dos restos
difere da ordem dos dígitos no resultado.
def representar(n, base):
return ""Verifica o código para comparar o resultado com os testes.
Testes
Zero tem um dígito
Código do testeprint(repr(representar(0, 2)))Esperado:'0'
Restos por ordem inversa
Código do testeprint(repr(representar(13, 2)))Esperado:'1101'
Zeros interiores
Código do testeprint(repr(representar(100, 10)))Esperado:'100'
Outra base
Código do testeprint(repr(representar(46, 7)))Esperado:'64'
Um dígito
Código do testeprint(repr(representar(5, 6)))Esperado:'5'
print(repr(representar(0, 2)))'0'
print(repr(representar(13, 2)))'1101'
print(repr(representar(100, 10)))'100'
print(repr(representar(46, 7)))'64'
print(repr(representar(5, 6)))'5'
Pista
Solução
Cada resto fica entre zero e base - 1, portanto cabe num único carácter
decimal para as bases permitidas. Retiramos esse dígito do prefixo e
acrescentamo-lo à esquerda do texto já construído.
def representar(n, base):
if n == 0:
return "0"
resultado = ""
while n > 0:
resultado = str(n % base) + resultado
n //= base
return resultadoPara 46 em base 7, o primeiro resto é 4 e o quociente 6; o segundo resto é 6
com quociente zero. O texto final é "64", pois .
A medida n diminui quando é positiva porque a base é pelo menos 2.
Sem o caso de zero, a função devolveria uma string vazia. Concatenar os restos
à direita daria a ordem errada para 13 em base 2, "1011".