Seja Bem-Vindo. Este site tem recursos de leitura de texto, basta marcar o texto e clicar no ícone do alto-falante   Click to listen highlighted text! Seja Bem-Vindo. Este site tem recursos de leitura de texto, basta marcar o texto e clicar no ícone do alto-falante

Lógica de Programação – Aula 02

Estruturas desvio, laços e recursão.

Introdução

Em seu estado mais simples, um programa executa instruções de forma linear, isto é, linha após linha, sem desvios e sem repetições. Esse é o ponto de partida natural de qualquer linguagem de programação: uma sequência ordenada de comandos que o computador interpreta obedientemente. Embora essencial para compreender a estrutura básica de um algoritmo, o fluxo linear é limitado; ele expressa apenas processos que seguem um caminho único e ininterrupto, incapaz de reagir a condições variáveis ou de repetir tarefas necessárias para resolver problemas mais complexos.

A partir dessa limitação surgem as estruturas de desvio (decisão), responsáveis por permitir que o programa escolha diferentes rotas de execução conforme características dos dados ou do ambiente. Com elas, um algoritmo passa a incorporar lógica condicional, avaliando situações e tomando decisões: “se isto for verdadeiro, faça X; caso contrário, faça Y”. Esse mecanismo transforma o programa de uma simples sequência estática em um sistema adaptável, capaz de responder a contextos diversos. O controle de fluxo condicional é, portanto, o primeiro sinal de “inteligência” que atribuímos a um algoritmo.

Fundamentos da Lógica de Programação: Um Guia Essencial - Oliveira Web

Complementando essas capacidades, as estruturas de repetição (laços) permitem que o programa execute um mesmo bloco de instruções múltiplas vezes, seja enquanto uma condição se mantiver verdadeira, seja percorrendo os elementos de uma coleção. Laços são cruciais para qualquer tarefa que envolva processamento contínuo, cálculos iterativos, manipulação de listas, automação e algoritmos matemáticos. São eles que reduzem o esforço de escrever código repetitivo, tornando-o mais compacto, eficiente e expressivo.

Por fim, a recursão eleva a lógica de controle de fluxo a um nível conceitual ainda mais alto, ao permitir que uma função invoque a si mesma para resolver problemas gradualmente menores. Recursão é uma ponte entre a matemática e a computação, fornecendo soluções elegantes para estruturas como árvores, grafos, sequências definidas por relações internas e processos naturalmente divididos em subproblemas.

Esses três pilares — decisão, repetição e recursão — compõem o conjunto fundamental de ferramentas que permitem ao estudante ir além do fluxo linear e começar a criar algoritmos verdadeiramente expressivos, inteligentes e generalizáveis. São eles a base de todo pensamento computacional moderno.

Decisão

As estruturas de decisão constituem um dos pilares fundamentais da lógica de programação, permitindo que um algoritmo escolha entre caminhos alternativos com base em condições previamente avaliadas. Diferentemente do fluxo linear — no qual as instruções são executadas sempre na mesma sequência — as estruturas condicionais introduzem flexibilidade e inteligência ao programa, possibilitando que ele reaja a diferentes valores de entrada, estados internos ou resultados intermediários. Em termos conceituais, a decisão permite modelar problemas reais em que a resposta depende de circunstâncias específicas, como verificar se um número é positivo, se um usuário possui permissão de acesso ou se determinado valor excede um limite estabelecido.

If…else statement | Hexainclude

No contexto do Python, a decisão é implementada principalmente por meio das sentenças if, elif e else, que expressam de forma clara e legível a lógica condicional do programa. Essa simplicidade sintática, aliada a operadores relacionais e lógicos, torna Python uma excelente porta de entrada para discutir formalmente temas como predicados, avaliação booleana e encadeamento de condições. A compreensão sólida dessas estruturas não apenas amplia a capacidade do estudante de resolver problemas computacionais simples, mas também prepara o terreno para tópicos mais avançados, como estruturas de repetição, algoritmos de busca e ordenação, e, finalmente, a construção de sistemas mais robustos e eficientes.

Valores booleanos

Os valores booleanos constituem a base lógica sobre a qual operam todas as estruturas de tomada de decisão em programação. Originados na álgebra de George Boole, esses valores representam apenas duas possibilidades fundamentais: verdadeiro e falso, expressos em Python como True e False. Embora pareçam simples, tais valores são a espinha dorsal dos sistemas algébricos utilizados para formular condições, construir predicados e controlar o fluxo de execução dos programas. Um programa só consegue “decidir” entre caminhos alternativos porque é capaz de avaliar expressões que resultam em valores booleanos, como comparações (x > 10) ou verificações de igualdade (nome == "Ana").

No contexto da lógica de programação e da sintaxe do Python, o valor booleano desempenha a função de critério para estruturas como if, elif e else. Quando o Python encontra uma instrução condicional, ele primeiro avalia a expressão lógica — que pode envolver operadores relacionais (>, <, ==, !=, >=, <=) e lógicos (and, or, not) — e determina se seu resultado é True ou False. A partir dessa avaliação, o fluxo do programa é desviado para o bloco de código correspondente. Compreender, portanto, o papel dos valores booleanos é essencial para que o estudante possa formular decisões computacionais corretas, modelar situações reais em termos lógicos e avançar posteriormente para estruturas mais complexas de raciocínio algorítmico. Veja a figura abaixo:

Operadores Booleanos | Resumo e Exercícios Resolvidos

Veja o resultado de algumas operações lógicas no ambiente interativo do Python.

Resultado de operações lógicas no terminal
Resultado de operações lógicas no terminal

Podemos fazer operação lógicas com textos. Veja abaixo:

Operações lógicas com textos
Operações lógicas com textos

Como funciona controle de fluxo IF, THEN, ELSE no Pyhton

O mecanismo de controle de fluxo por meio das estruturas condicionais é uma das ferramentas mais importantes na construção de algoritmos, pois permite que um programa escolha caminhos distintos de execução com base em condições lógicas. Em Python, esse controle é realizado pelas instruções if, elif e else. Embora linguisticamente equivalentes às ideias clássicas de IF–THEN–ELSE da lógica algorítmica, em Python não utilizamos as palavras “then” ou “fim-se”; a linguagem adota um estilo sintático mais enxuto, baseado no uso de dois pontos (:) e indentação para delimitar blocos de código. Assim, sempre que o interpretador encontra um if, ele avalia uma expressão booleana. Se essa expressão resultar em True, o bloco imediatamente abaixo, devidamente indentado, será executado.

Via Rápida | Estruturas de decisão

Quando a primeira condição (if) não é satisfeita, Python permite ampliar a lógica decisória utilizando a palavra-chave elif (“else if”), que introduz uma nova condição a ser testada. Caso nenhuma das condições anteriores seja verdadeira, a cláusula else pode ser utilizada para fornecer um caminho alternativo de execução, garantindo que o programa sempre tenha uma saída lógica mesmo quando todas as condições forem falsas. Essa estrutura é especialmente poderosa para modelar cenários em que diferentes casos precisam ser tratados de forma mutuamente exclusiva, como classificações, validação de dados e seleção de ações distintas com base em entradas do usuário.

8: Fluxograma e sintaxe -Instrução decisão se-então-senão ...

Em síntese, o controle de fluxo via if–elif–else transforma programas estáticos em sistemas dinâmicos capazes de reagir a diferentes situações, tornando o algoritmo mais expressivo, matematicamente estruturado e semanticamente próximo da lógica formal. Essa compreensão é essencial não apenas para resolver problemas comuns, mas também para o aluno avançar futuramente para tópicos como laços de repetição, funções, estruturas de dados e técnicas de resolução algorítmica mais sofisticadas.

Exemplos

Exemplo 1 — Verificar se um número é positivo

Este exemplo apresenta o uso do if elemental, ideal para o primeiro contato com tomada de decisão.

numero = 10

if numero > 0:
    print("O número é positivo.")

Explicação:
A expressão numero > 0 é avaliada. Como resulta em True, o bloco indentado é executado.
Caso fosse False, nada aconteceria (pois não há else, ainda).


Exemplo 2 — Classificar um número como positivo, negativo ou zero

Aqui introduzimos elif e else, permitindo múltiplos caminhos mutuamente exclusivos.

numero = 0

if numero > 0:
    print("O número é positivo.")
elif numero < 0:
    print("O número é negativo.")
else:
    print("O número é zero.")

Explicação:
O programa testa cada condição na ordem.
A primeira verdadeira determina o bloco executado; as demais são ignoradas.
É o exemplo clássico de árvore decisória simples.


Exemplo 3 — Verificar senha (sem loops)

Mostra como decisões permitem validar dados sem estruturas repetitivas.

senha_correta = "python123"
entrada = "python123"

if entrada == senha_correta:
    print("Acesso permitido.")
else:
    print("Acesso negado.")

Explicação:
A expressão booleana compara duas strings.
A igualdade (==) retorna True ou False, guiando o fluxo.


Exemplo 4 — Determinar faixa etária simples

Excelente para demonstrar encadeamento lógico simples.

idade = 17

if idade >= 18:
    print("Maior de idade.")
else:
    print("Menor de idade.")

Explicação:
O Python avalia a condição e escolhe um dos dois caminhos possíveis.
Ideal para introduzir operadores relacionais.


Exemplo 5 — Desconto básico baseado em valor de compra

Mostra como decisões refletem regras de negócio.

valor = 120.0

if valor >= 100:
    print("Desconto aplicado: R$ 10,00")
else:
    print("Sem desconto.")

Explicação:
O critério é objetivo: compras acima de um valor-limite recebem benefício.
Introduz a noção de decisão determinística baseada em parâmetros quantitativos.

IFs Aninhados

As estruturas condicionais aninhadas representam uma extensão natural do mecanismo básico de decisão, permitindo que um teste lógico seja realizado dentro de outro. Em termos conceituais, trata-se de um recurso empregado quando a escolha apropriada depende de duas ou mais camadas de decisões, de forma hierárquica. Assim como em situações cotidianas nas quais decidimos algo somente após verificar outra condição preliminar, os IFs aninhados possibilitam que o algoritmo refine progressivamente sua lógica, aplicando novos critérios apenas quando os anteriores foram satisfeitos. Essa abordagem é útil para casos em que a solução não se limita a uma simples comparação, mas envolve cenários condicionais mais elaborados.

Em Python, o aninhamento é realizado simplesmente colocando um novo bloco if dentro de outro bloco if, mantendo a indentação adequada. Essa organização deixa explícita a sequência lógica das decisões e garante que o programador compreenda de forma clara qual condição está subordinada à anterior. Embora o aninhamento seja sintaticamente simples, é pedagogicamente importante destacar que o excesso de camadas pode comprometer a legibilidade do código. Por isso, muitos algoritmos práticos utilizam elif para evitar níveis de profundidade desnecessários, reservando o aninhamento apenas para casos em que as decisões realmente dependem umas das outras.


Exemplos Ilustrativos Para Iniciantes

Exemplo 1 — Verificar se um número é positivo e, se for, se é maior que 100

numero = 150

if numero > 0:
    print("O número é positivo.")
    if numero > 100:
        print("O número é também maior que 100.")

Explicação:
O segundo if só é avaliado se a primeira condição for verdadeira.
Demonstra claramente a hierarquia de decisões.


Exemplo 2 — Determinar categoria de nota

nota = 85

if nota >= 0:
    if nota >= 70:
        print("Aprovado.")
    else:
        print("Recuperação.")
else:
    print("Valor inválido.")

Explicação:
Primeiro verifica-se se a nota é válida; somente então avalia-se aprovação ou recuperação.
Mostra como decisões preliminares protegem o programa contra entradas incorretas.


Exemplo 3 — Autorização simples com duplo critério

usuario = "admin"
senha = "1234"

if usuario == "admin":
    if senha == "1234":
        print("Acesso liberado.")
    else:
        print("Senha incorreta.")
else:
    print("Usuário desconhecido.")

Explicação:
O algoritmo só verifica a senha se o usuário for válido, exemplificando dependência lógica entre decisões.


Exemplo 4 — Classificação de número par/ímpar apenas se for positivo

numero = 8

if numero > 0:
    if numero % 2 == 0:
        print("Número positivo e par.")
    else:
        print("Número positivo e ímpar.")
else:
    print("Número não é positivo.")

Explicação:
Condições aninhadas permitem uma classificação mais rica e precisa.


Exemplo 5 — Determinação de categoria de idade

idade = 12

if idade >= 0:
    if idade < 13:
        print("Criança.")
    else:
        print("Maior ou igual a 13 anos.")
else:
    print("Idade inválida.")

Explicação:
Novamente o primeiro teste garante validade; o segundo classifica.
Exemplo típico de lógica escalonada.

Mais exemplos

1. Verificar se um número é positivo e se é par

Enunciado:
Leia um número e exiba se ele é positivo e, dentro dessa condição, se também é par.

Gabarito comentado:

n = 8

if n > 0:
    print("Número positivo.")
    if n % 2 == 0:
        print("E também é par.")

Comentário:
O segundo teste só é avaliado quando o primeiro é verdadeiro. Demonstra dependência.


2. Verificar se idade é válida e classificar como criança/adulto

Enunciado:
Considere uma idade. Primeiro verifique se é um valor válido (≥ 0). Se for, classifique como “criança” se < 12, e “adulto” caso contrário.

Gabarito comentado:

idade = 10

if idade >= 0:
    if idade < 12:
        print("Criança.")
    else:
        print("Adulto.")
else:
    print("Idade inválida.")

Comentário:
O aninhamento garante que apenas idades válidas sejam classificadas.


3. Verificar se uma senha tem tamanho correto e, em seguida, se é igual à senha oficial

Enunciado:
Dada uma senha, verifique se ela possui pelo menos 4 caracteres. Se sim, teste se é igual à senha definida.

Gabarito comentado:

senha = "abcd"
senha_oficial = "abcd"

if len(senha) >= 4:
    if senha == senha_oficial:
        print("Acesso liberado.")
    else:
        print("Senha incorreta.")
else:
    print("Senha muito curta.")

Comentário:
A validação estrutural (tamanho) precede a validação lógica (comparação).


4. Verificar se um número está dentro de um intervalo e se é múltiplo de 3

Enunciado:
Considere um número. Verifique se ele está entre 1 e 100 e, dentro desse intervalo, se é múltiplo de 3.

Gabarito comentado:

n = 27

if 1 <= n <= 100:
    print("Número dentro do intervalo.")
    if n % 3 == 0:
        print("É múltiplo de 3.")
else:
    print("Fora do intervalo.")

Comentário:
Exemplo ideal para mostrar como intervalos e múltiplos podem ser combinados.


5. Verificar se um nome não está vazio e se começa com letra maiúscula

Enunciado:
Dada uma string, verifique se ela não é vazia. Se não for, verifique se seu primeiro caractere é maiúsculo.

Gabarito comentado:

nome = "Python"

if nome != "":
    if nome[0].isupper():
        print("Nome começa com maiúscula.")
    else:
        print("Primeira letra minúscula.")
else:
    print("Nome inválido.")

Comentário:
Combina verificação de estrutura (string vazia) com análise de caractere.


6. Classificação simples de nota com proteção de entrada

Enunciado:
Verifique se uma nota é válida (0 a 100). Caso seja, exiba “alta” se ≥ 80, ou “baixa” caso contrário.

Gabarito comentado:

nota = 92

if 0 <= nota <= 100:
    if nota >= 80:
        print("Nota alta.")
    else:
        print("Nota baixa.")
else:
    print("Nota inválida.")

Comentário:
Ilustra como validar faixa antes de classificar, algo recorrente em programação real.


7. Verificar se a temperatura está acima do ponto de ebulição da água apenas se for positiva

Enunciado:
Dada uma temperatura, verifique se é positiva e, sendo positiva, se é ≥ 100.

Gabarito comentado:

temp = 120

if temp > 0:
    if temp >= 100:
        print("Acima do ponto de ebulição da água.")
else:
    print("Temperatura não é positiva.")

Comentário:
Demonstra encadeamento direto e simples.


8. Verificar se uma letra é vogal, mas somente se for uma letra do alfabeto

Enunciado:
Dado um caractere, verifique se é uma letra. Se for, então verifique se é vogal.

Gabarito comentado:

caractere = "a"

if caractere.isalpha():
    if caractere.lower() in "aeiou":
        print("É vogal.")
    else:
        print("É consoante.")
else:
    print("Não é uma letra.")

Comentário:
Perfeito para discutir testes de tipo e refinamento de condições.


9. Verificar se número é par apenas se for maior que 50

Enunciado:
Dado um número, teste se é maior que 50. Se for, verifique se é par.

Gabarito comentado:

n = 72

if n > 50:
    if n % 2 == 0:
        print("Maior que 50 e par.")
    else:
        print("Maior que 50 e ímpar.")
else:
    print("Não é maior que 50.")

Comentário:
Reforça que o segundo teste depende do primeiro.


10. Verificar se ano é válido e se é bissexto (versão simplificada)

Enunciado:
Dado um ano, verifique se é > 0. Se for, verifique se é divisível por 4 (simplificação didática).

Gabarito comentado:

ano = 2024

if ano > 0:
    if ano % 4 == 0:
        print("Ano bissexto (versão simples).")
    else:
        print("Ano comum.")
else:
    print("Ano inválido.")

Comentário:
Embora a regra real seja mais complexa, a versão simples é ótima pedagogicamente.

Laços de Repetição em Python

1. Introdução Conceitual

As estruturas de repetição — também chamadas de laços ou loops — constituem um dos mecanismos fundamentais da lógica de programação. Seu propósito é permitir que um conjunto de instruções seja executado múltiplas vezes, de forma controlada e previsível. Em termos formais, os laços respondem à necessidade de lidar com padrões repetitivos presentes em quase todos os problemas computacionais, como percorrer listas, processar dados, gerar sequências numéricas ou realizar verificações sucessivas.

Do ponto de vista lógico, um laço representa a materialização da ideia de iterações, isto é, a repetição controlada de um bloco de código enquanto uma condição permanece válida ou enquanto existirem elementos a serem processados. Em Python, os dois principais tipos de laços são:

  1. for, baseado em iteração sobre coleções;

  2. while, baseado em uma condição booleana.

Ambos são essenciais para a construção de algoritmos eficientes, e sua compreensão adequada é indispensável para qualquer estudante que inicia no universo da programação.


2. O Laço for

2.1. Conceito

O laço for em Python foi projetado para iterar sobre uma sequência de elementos, como listas, tuplas, strings, intervalos (ranges) e até objetos mais complexos. Ao contrário de outras linguagens em que for exige controle explícito de contadores, o for do Python adota um modelo de iteração direta, mais legível e consistente com a filosofia da linguagem.

Em termos formais:

O laço for itera sobre cada elemento de um objeto iterável, executando o bloco de código associado para cada item.

2.2. Exemplo Básico

for letra in "Python":
    print(letra)

Saida:

P

y

t

h

o

n

Explicação:
O laço percorre cada caractere da string e o imprime.
Cada interação dá a variável letra um valor diferente.

2.3. Uso com range()

range() permite gerar sequências numéricas, frequentemente utilizadas como contadores.

for i in range(5):
    print(i)

Saída:
0
1
2
3
4

Comentário:
range(5) gera 5 valores, de 0 a 4. Esse tipo de iteração é muito usado ao simular laços tradicionais baseados em contadores.

2.4. Iterando sobre listas

frutas = ["maçã", "banana", "uva"]

for fruta in frutas:
    print(fruta)

Saida:

maçã

banana

uva

2.5. Iteração com índice e valor — enumerate

for indice, valor in enumerate(["a", "b", "c"]):
    print(indice, valor)

Saida:

0 a

1 b

2 c


3. O Laço while

3.1. Conceito

O laço while executa seu bloco de instruções enquanto uma condição for verdadeira. Ele é adequado para situações em que o número de repetições não é previamente conhecido, dependendo de eventos ou estados internos do programa.

Formalmente:

O laço while repete instruções enquanto a expressão condicional se mantém verdadeira.

3.2. Exemplo Básico

contador = 1

while contador <= 5:
    print(contador)
    contador += 1

Saida:

1

2

3

4

5

Explicação:
O laço só termina quando a condição contador <= 5 deixa de ser verdadeira.
Demonstra perfeitamente a relação entre condição → repetição.

3.3. Exemplo com condição de parada lógica

senha = ""

while senha != "python":
    senha = input("Digite a senha: ")

print("Acesso permitido.")

Comentário:
O laço só termina quando o usuário digita a senha correta.
Exemplo clássico da dependência entre lógica booleana e repetição.


4. Controlando Laços: break e continue

4.1. break — Interrupção imediata

Interrompe o laço, independentemente da condição.

for n in range(10):
    if n == 5:
        break
    print(n)

Saida:

0

1

2

3

4

4.2. continue — Pula para a próxima iteração

for n in range(6):
    if n == 3:
        continue
    print(n)

Saida:

0

1

2

4

5

Comentário:
Mostra ao estudante como manipular o fluxo interno do laço, algo essencial para lógica condicional aplicada.


5. Laços Aninhados

Um laço pode conter outro dentro de si, formando iterações hierárquicas. Essa técnica aparece em algoritmos de busca, matrizes, geração de combinações, entre outros.

for i in range(3):
    for j in range(2):
        print("i =", i, "j =", j)

Saida:

i = 0 j = 0

i = 0 j = 1

i = 1 j = 0

i = 1 j = 1

i = 2 j = 0

i = 2 j = 1


6. Comparação: for x while

Aspecto for while
Natureza Iteração sobre sequência Controle baseado em condição
Número de repetições Normalmente conhecido Muitas vezes imprevisível
Riscos Baixos Loop infinito se condição não mudar
Clareza Alta Moderada, depende da lógica

 

Resumo:

  • Use for quando houver sequência definida.
  • Use while quando a repetição depender de uma condição variável.

 

7. Exemplos

Exemplo 1 — Somar números de 1 a 5 com for

soma = 0
for n in range(1, 6):
    soma += n
print(soma)

Saida: 

15

Exemplo 2 — Aguardar dado correto com while

x = 0
while x != 10:
    x = int(input("Digite 10: "))

Exemplo 3 — Contar vogais em uma palavra

palavra = "Python"
cont = 0

for letra in palavra:
    if letra.lower() in "aeiou":
        cont += 1

print("Vogais:", cont)

Exemplo 4 — Exemplo de laço aninhado (tabuada simplificada)

for i in range(1, 4):
    for j in range(1, 4):
        print(i, "x", j, "=", i*j)

Exercícios


1. Imprimir os números de 1 a 5 usando um laço for

Enunciado:
Utilize um laço for para exibir os números de 1 a 5, um por linha.

Gabarito comentado

for i in range(1, 6):
    print(i)

Comentário:
range(1, 6) gera 1,2,3,4,5. Este é o exercício mais básico para ilustrar a mecânica do for em sequência numérica.


2. Exibir cinco vezes a frase “Python é ótimo!”

Enunciado:
Usando um laço, imprima a frase “Python é ótimo!” cinco vezes.

Gabarito comentado

for _ in range(5):
    print("Python é ótimo!")

Comentário:
O uso de _ indica que o contador não será usado. Importante para introduzir boas práticas.


3. Somar os números de 1 a 10 com for

Enunciado:
Calcule a soma dos números de 1 a 10 usando um laço for.

Gabarito comentado

soma = 0

for i in range(1, 11):
    soma += i

print(soma)

Comentário:
Exercício clássico para demonstrar acumuladores. Prepara terreno para algoritmos básicos.


4. Contar regressivamente de 10 a 1

Enunciado:
Utilize um laço para exibir uma contagem regressiva de 10 até 1.

Gabarito comentado

for i in range(10, 0, -1):
    print(i)

Comentário:
Aqui introduz-se o terceiro parâmetro do range: passo negativo. Essencial para estrutura mental do aluno.


5. Imprimir os caracteres de uma string, um por linha

Enunciado:
Dada a string "Python", exiba cada caractere separadamente usando um laço.

Gabarito comentado

texto = "Python"

for letra in texto:
    print(letra)

Comentário:
Mostra que for percorre qualquer iterável, não apenas sequências numéricas.


6. Exibir números pares de 0 a 20 utilizando while

Enunciado:
Use um laço while para imprimir todos os números pares entre 0 e 20.

Gabarito comentado

i = 0

while i <= 20:
    print(i)
    i += 2

Comentário:
Demonstração clara de controle manual de variáveis, uma característica típica do while.


7. Ler números até que o usuário digite 0 e somar todos (estrutura clássica de repetição condicional)

Enunciado:
Utilize while para ler números indefinidamente até que o usuário informe 0. Exiba a soma dos valores digitados.

(Para manter exemplo offline, valores são fixados.)

Gabarito comentado

numeros = [5, 3, 2, 0]  # simulação
soma = 0
i = 0

while numeros[i] != 0:
    soma += numeros[i]
    i += 1

print(soma)

Comentário:
Introduz repetição condicionada a um “sentinela”. Padrão usado amplamente em sistemas reais.


8. Encontrar o maior número em uma lista usando laço for

Enunciado:
Com uma lista de valores, percorra-a para encontrar o maior número sem usar funções prontas.

Gabarito comentado

lista = [10, 5, 8, 22, 3]
maior = lista[0]

for n in lista:
    if n > maior:
        maior = n

print(maior)

Comentário:
Exercício fundamental que aproxima o aluno de algoritmos clássicos (ex.: busca linear).


9. Criar um contador usando while que exibe valores de 1 a 10

Enunciado:
Imprima os números de 1 a 10 utilizando exclusivamente um laço while.

Gabarito comentado

i = 1

while i <= 10:
    print(i)
    i += 1

Comentário:
Mostra o controle manual de incremento e reforça o risco de loops infinitos caso o incremento seja esquecido.


10. Multiplicar todos os valores de uma lista pelo número 2

Enunciado:
Utilize um laço para criar uma nova lista contendo cada elemento da lista original multiplicado por 2.

Gabarito comentado

original = [1, 2, 3, 4, 5]
nova = []

for n in original:
    nova.append(n * 2)

print(nova)

Comentário:
Exercício que introduz transformação de coleções, preparando para list comprehension futuramente.


Desafio (Exercicios extraidos do livro Algoritmos Estruturados – Harry Farrer e outros – LTC)

Exercicio 01

Fazer um algoritmo que:

  • Leia um número indeterminado de linhas contendo cada uma a idade de um indivíduo.
  • A última linha que não entrará nos cálculos, contém o valor da idade igual a zero.
  • Calcule e escreva a idade média deste grupo de indivíduos.
Resposta

Solução do Problema — Cálculo da Idade Média (com 0 como sentinela)

A proposta exige a leitura de idades até que o usuário digite 0, valor que não deve ser contabilizado. Esse tipo de mecanismo é conhecido como valor sentinela, pois marca o fim da entrada de dados.

Como ainda estamos utilizando apenas conteúdos básicos, a solução emprega:

  • um laço while,

  • um acumulador de somatório,

  • um contador de indivíduos,

  • e um cálculo simples da média.


Código em Python (com comentários pedagógicos)

soma = 0          # Acumula todas as idades válidas
contador = 0      # Conta quantas idades foram digitadas

idade = int(input("Digite uma idade (0 para encerrar): "))

while idade != 0:
    soma += idade          # Adiciona a idade ao somatório
    contador += 1          # Incrementa o total de pessoas

    idade = int(input("Digite uma idade (0 para encerrar): "))

# Após a leitura, verificamos se houve dados válidos
if contador > 0:
    media = soma / contador
    print("Idade média:", media)
else:
    print("Nenhuma idade válida foi informada.")

Explicação

  1. Inicialização dos acumuladores
    Antes do laço, criamos variáveis para acumular a soma das idades e contar quantos indivíduos foram registrados. Essa é a estrutura clássica de um algoritmo iterativo de média aritmética.

  2. Primeira leitura antes do laço
    A leitura da idade ocorre antes do while. Isso permite testar a condição de entrada — se for zero, o laço nem começa. Esse padrão é conhecido como priming read (leitura de preparação).

  3. Laço condicionado por sentinela
    O laço while idade != 0: executa enquanto as entradas forem válidas. Cada idade é somada e contabilizada.

  4. Nova leitura dentro do laço
    O algoritmo solicita outra idade ao final de cada iteração, preservando o comportamento contínuo de coleta até que o usuário digite zero.

  5. Cálculo da média
    Após o laço, calculamos soma / contador.
    Há uma proteção para o caso do contador ser zero, prevenindo erro de divisão por zero.


Notas didáticas

  • Este problema é clássico em lógica de programação e serve para apresentar laços com valor sentinela, um padrão amplamente utilizado em diversas linguagens.
  • Também reforça a importância de variáveis acumuladoras, contadores, e validação final antes de cálculos, práticas fundamentais para o pensamento algorítmico.

 

Exercicio 02

Tem-se um conjunto de dados contendo a altura e o sexo (masculino, feminino) de 50 pessoas. Fazer um algoritmo que calcule e escreva:

  • a maior e a menor altura do grupo;
  • a média de altura das mulheres;
  • o número de homens;
Resposta

Problema 2 — Altura e sexo de 50 pessoas

Enunciado (reformulado para clareza)

Ler a altura e o sexo (“M” para masculino ou “F” para feminino) de 50 pessoas.
Ao final, o algoritmo deve informar:

  1. A maior altura do grupo.

  2. A menor altura do grupo.

  3. A média das alturas das mulheres.

  4. O número de homens no grupo.


Solução em Python

# Inicialização das variáveis de controle
maior_altura = 0
menor_altura = 9999   # valor inicial alto para garantir substituição
soma_altura_mulheres = 0
quant_mulheres = 0
quant_homens = 0

# Como são 50 pessoas, podemos usar um laço for
for i in range(50):
    print("Pessoa", i + 1)

    altura = float(input("Digite a altura (em metros): "))
    sexo = input("Digite o sexo (M/F): ")

    # Atualiza maior altura
    if altura > maior_altura:
        maior_altura = altura

    # Atualiza menor altura
    if altura < menor_altura:
        menor_altura = altura

    # Contagem por sexo e soma das alturas das mulheres
    if sexo == "F" or sexo == "f":
        soma_altura_mulheres += altura
        quant_mulheres += 1
    else:
        quant_homens += 1

# Cálculo da média das alturas das mulheres
if quant_mulheres > 0:
    media_mulheres = soma_altura_mulheres / quant_mulheres
else:
    media_mulheres = 0  # Proteção contra divisão por zero

# Exibição dos resultados
print("\nRESULTADOS:")
print("Maior altura do grupo:", maior_altura, "m")
print("Menor altura do grupo:", menor_altura, "m")
print("Média da altura das mulheres:", media_mulheres, "m")
print("Número de homens:", quant_homens)

Explicação conceitual 

1. Controle das alturas
  • maior_altura começa em 0 porque qualquer altura verdadeira será maior que isso.

  • menor_altura começa em um número muito grande para garantir substituição imediata na primeira entrada.

2. Separação por sexo

Como ainda estamos nos primeiros passos, usamos uma condicional simples:

if sexo == "F" or sexo == "f":

Essa abordagem permite ao estudante compreender:

  • comparação de strings;

  • fluxo decisório básico;

  • contagem por categorias.

3. Média das mulheres

O cálculo só é realizado se quant_mulheres > 0; isso evita erro de divisão por zero e permite demonstrar a importância de validações.

4. Laço for

O uso de for é adequado, pois sabemos previamente o número total de indivíduos (50).
Reforça-se o conceito:

for i in range(50):

que executa o bloco exatamente 50 vezes, numerando cada pessoa no processo.


 

Exercicio 03

Uma universidade deseja fazer um levantamento a respeito do seu concurso vestibular. Para cada curso, é fornecido o seguinte conjunto de valores:

  • o código do curso;
  • o número de vagas;
  • número de candidatos do sexo masculino;
  • número de candidatos do sexo feminino;

O último conjunto, para indicar fim de dados, contém o código do curso igual a zero. Fazer um algoritmo que:

  • calcule escreva, para cada curso, o número de candidatos por vaga e a porcentagem de candidatos do sexo feminino (escreva também o código correspondente do curso);
  • determine o maior número de candidatos por vaga e escreva esse número juntamente com o código do curso correspondente (supor que não haja empate);
  • calcule e escreva o total de candidatos;
Resposta

Solução do Problema — Levantamento de Cursos no Vestibular

Enunciado (relembrando)

Para cada curso são fornecidos:

  • código do curso;

  • número de vagas;

  • número de candidatos do sexo masculino;

  • número de candidatos do sexo feminino.

O código 0 indica o fim da entrada.

O algoritmo deve:

  1. Calcular e escrever, para cada curso:

    • candidatos por vaga;

    • porcentagem de mulheres;

    • código do curso.

  2. Determinar o maior número de candidatos por vaga entre todos os cursos e escrever esse valor e seu código.

  3. Calcular e escrever o total de candidatos.


Solução comentada em Python
# Inicializações antes do laço
maior_relacao = 0                  # Guarda o maior número de candidatos por vaga encontrado
codigo_maior_relacao = 0           # Guarda o código do curso correspondente
total_candidatos = 0               # Soma total de candidatos de todos os cursos

while True:
    # Leitura dos dados
    codigo = int(input("Código do curso (0 para sair): "))
    
    if codigo == 0:
        break  # Encerra o laço quando código for 0

    vagas = int(input("Número de vagas: "))
    homens = int(input("Número de candidatos homens: "))
    mulheres = int(input("Número de candidatas mulheres: "))

    # Cálculos necessários
    total = homens + mulheres
    relacao = total / vagas
    porcentagem_mulheres = (mulheres * 100) / total

    # Escrita dos dados do curso
    print("\nCurso:", codigo)
    print("Candidatos por vaga:", relacao)
    print("Porcentagem de mulheres:", porcentagem_mulheres, "%\n")

    # Atualizar maior relação candidatos/vaga
    if relacao > maior_relacao:
        maior_relacao = relacao
        codigo_maior_relacao = codigo

    # Atualizar total geral de candidatos
    total_candidatos += total

# Escrita final dos resultados gerais
print("Maior número de candidatos por vaga:", maior_relacao)
print("Curso correspondente:", codigo_maior_relacao)
print("Total de candidatos:", total_candidatos)

Comentários Didáticos

1. Estrutura de repetição com sentinela

O uso de while True: e do teste if codigo == 0: break representa um padrão clássico de laço com valor sentinela.
O sentinela não participa dos cálculos, apenas encerra o algoritmo.

2. Cálculo “candidatos por vaga”

A relação candidatos/vaga é simplesmente:

    \[ \text{relação} = \frac{\text{total de candidatos}}{\text{número de vagas}} \]

Esse número é essencial em análises de vestibulares e processos seletivos.

3. Porcentagem de mulheres

    \[ \text{porcentagem} = \frac{\text{mulheres} \times 100}{\text{total de candidatos}} \]

4. Controle de máximo

O programa verifica repetidamente se a relação atual é maior do que a maior já encontrada, atualizando as variáveis apropriadas.

5. Somatório total dos candidatos

Cumpre o requisito 3 do enunciado.


Recursão

Introdução à Recursão

Recursão é uma técnica de projeto de algoritmos na qual uma função chama a si mesma para resolver instâncias menores (ou mais simples) do mesmo problema. Conceitualmente, recursão é a aplicação do princípio divide-e-conquista em que um problema complexo é decomposto em subproblemas análogos de menor porte, resolvidos repetidamente até atingir um caso base trivial. Em matemática, recursão aparece naturalmente em definições por recorrência (sequência de Fibonacci, fatorial, definições de árvores) e, em computação, fornece uma forma direta e elegante de expressar soluções para estruturas recursivas (árvores, grafos, listas encadeadas) e para algoritmos que têm auto-referência na sua definição.

Pedagogicamente, recursão serve a dois propósitos fundamentais: (1) é uma ponte conceitual entre pensamento matemático (indução/recorrência) e construção de programas; (2) oferece uma forma de expressão que muitas vezes resulta em soluções curtas, legíveis e próximas da especificação matemática do problema. Ensinar recursão cedo, com rigor, ajuda o estudante a compreender noções fundamentais como invariantes de recursão, caso base, profundidade de recursão e complexidade recursiva — conceitos que reaparecem em algoritmos e estruturas de dados avançadas.


Estrutura mínima de uma função recursiva

Toda definição recursiva bem formada contém pelo menos dois elementos:

  1. Caso base (base case) — condição que não faz chamada recursiva e retorna um resultado simples e imediato. Sem caso base, a recursão nunca termina.

  2. Caso recursivo (recursive case) — passo que reduz o problema a uma (ou mais) instância(s) menor(es) do mesmo problema e combina seus resultados para produzir a solução final.

Exemplo esquemático (pseudo-Python):

def f(n):
    if caso_base(n):
        return resposta_simples
    else:
        return combinar(f(n_reduzido))

A correta escolha do caso base e da redução garante tanto a correção (o algoritmo produz o resultado certo) quanto a terminação (o algoritmo para).


Exemplo clássico: fatorial (com explicação)

Perfeito — vamos analisar este programa recursivo de forma didática, passo a passo, com atenção à semântica, ao estado da memória (frames de ativação) e ao cálculo numérico detalhado.

def fatorial(n):
    if n == 0:
        return 1                 # caso base
    else:
        return n * fatorial(n-1) # caso recursivo
        

print(fatorial(5))

1. O que faz a função — visão geral

A função fatorial implementa a definição matemática do fatorial de um número inteiro não-negativo:

  • caso base: (0! = 1);
  • caso recursivo: (n! = n \times (n-1)!) para (n>0).

Em Python, a função testa se n == 0. Se for verdade, retorna 1 imediatamente (caso base). Caso contrário, calcula fatorial(n-1) e multiplica esse resultado por n — isto é, delega a sub-atividade para a mesma função com argumento menor e, ao receber o resultado, realiza a multiplicação.

2. Complexidade e observações práticas

  • Complexidade de tempo: (O(n)) — a função faz uma chamada recursiva por cada inteiro positivo até 0.
  • Uso de memória (profundidade de pilha): (O(n)) — cada chamada cria um novo frame (registro de ativação) na pilha até atingir o caso base.
  • Limitação: para n muito grande a recursão pode estourar o limite de recursão do Python (RecursionError). Em Python não há otimização de tail-call; uma versão iterativa evita o crescimento linear da pilha.
  • Domínio: a função pressupõe n inteiro não-negativo. Para n < 0 ocorrerá recursão infinita até o limite do interpretador.

3. Traçado de execução (call stack) para fatorial(5) — passo a passo

A chamada inicial é fatorial(5). Vamos mostrar as chamadas feitas, como cada frame armazena sua própria cópia da variável n, e como os valores são computados na volta (unwinding) da pilha.

Chamadas (ordem de empilhamento)
  1. fatorial(5) — antes de poder retornar, precisa do valor de fatorial(4).
  2. fatorial(4) — precisa de fatorial(3).
  3. fatorial(3) — precisa de fatorial(2).
  4. fatorial(2) — precisa de fatorial(1).
  5. fatorial(1) — precisa de fatorial(0).
  6. fatorial(0) — caso base: retorna 1.

Cada uma dessas chamadas criou um frame na pilha com sua própria variável local n. Importante: o valor de n em um frame é imutável enquanto esse frame existe; a recursão não “altera” n, ela cria um novo frame com novo n-1.

Estado da pilha no momento imediatamente antes do retorno do caso base

(top = frame mais recente)

  • Frame 6: fatorial(0)n = 0
  • Frame 5: fatorial(1)n = 1
  • Frame 4: fatorial(2)n = 2
  • Frame 3: fatorial(3)n = 3
  • Frame 2: fatorial(4)n = 4
  • Frame 1: fatorial(5)n = 5 (frame inicial)

Unwinding — retornos e multiplicações (ordem de cálculo)

Agora a execução retorna do caso base e cada frame calcula seu resultado:

  1. fatorial(0) retorna 1 para o chamador (fatorial(1)).

  2. Em fatorial(1) temos return 1 * fatorial(0). Substituindo o valor recebido:

    • fatorial(1) = 1 * 1 = 1.
      fatorial(1) retorna 1 para fatorial(2).

  3. Em fatorial(2) temos return 2 * fatorial(1). Substituindo:

    • fatorial(2) = 2 * 1 = 2.
      fatorial(2) retorna 2 para fatorial(3).

  4. Em fatorial(3) temos return 3 * fatorial(2). Substituindo:

    • fatorial(3) = 3 * 2 = 6.
      fatorial(3) retorna 6 para fatorial(4).

  5. Em fatorial(4) temos return 4 * fatorial(3). Substituindo:

    • fatorial(4) = 4 * 6 = 24.
      (cálculo detalhado: (6 \times 4 = 24), ou ( (6 \times 2) \times 2 = 12 \times 2 = 24)).
      fatorial(4) retorna 24 para fatorial(5).

  6. Em fatorial(5) temos return 5 * fatorial(4). Substituindo:

    • fatorial(5) = 5 * 24. Vamos calcular detalhadamente:

      • (24 \times 5 = (20 \times 5) + (4 \times 5) = 100 + 20 = 120.)
        Portanto fatorial(5) = 120. Esse valor é retornado ao chamador original (print).

O print(fatorial(5)) exibirá:

120

4. Representação tabular dos frames (durante a execução)

Uma forma útil para alunos é ver uma tabela com cada frame, seu n e o valor retornado após o unwind:

Frame (chamada) Valor de n Ação efetuada Valor retornado
fatorial(5) 5 Espera resultado de fatorial(4) 120
fatorial(4) 4 Espera resultado de fatorial(3) 24
fatorial(3) 3 Espera resultado de fatorial(2) 6
fatorial(2) 2 Espera resultado de fatorial(1) 2
fatorial(1) 1 Espera resultado de fatorial(0) 1
fatorial(0) 0 Caso base: retorna 1 imediatamente 1

 

5. Observações finais 

  • Imutabilidade local de n: cada chamada tem sua própria cópia de n no frame. Não há “incremento” ou “decremento in-place” de uma única variável — há várias variáveis n diferentes, uma por frame.
  • Caso base obrigatório: sem if n == 0: return 1 a recursão seria infinita (para entradas não-negativas) e resultaria em RecursionError.
  • Visibilidade: variáveis locais em frames mais profundos (por exemplo o n em fatorial(2)) não são visíveis diretamente nos frames superiores — a comunicação é feita apenas pelo valor retornado.
  • Alternativa iterativa: para grandes n é mais seguro usar versão iterativa (laço) para evitar estouro de pilha.

Aspectos de implementação e memória (call stack)

Cada chamada recursiva cria uma frame na pilha de chamadas (call stack) contendo parâmetros, variáveis locais e endereço de retorno. Em Python, a profundidade máxima da pilha é limitada (valor padrão ~1000), e chamadas excessivas causam RecursionError: maximum recursion depth exceeded. Alterar esse limite com sys.setrecursionlimit() é possível, mas perigoso — aumenta o risco de estouro de memória e não resolve problemas de complexidade algorítmica. Portanto, para problemas com profundidade grande ou indefinida (por ex., percorrer listas muito longas), muitas vezes é mais seguro usar implementação iterativa ou técnicas de conversão de recursão para iteração.


Complexidade e desempenho — quando usar recursão

Recursão pode ser elegante, mas nem sempre é eficiente. Problemas recursivos podem gerar subproblemas que se repetem (ex.: Fibonacci recursivo clássico), levando a explosão combinatória e custo exponencial. Duas técnicas mitigadoras são:

  • Memoização (caching): armazenar resultados de chamadas já computadas para evitar recomputação (top-down DP).
  • Programação dinâmica (tabulação): transformar a recursão em uma rotina iterativa que preenche uma tabela de resultados (bottom-up DP).

Além disso, alguns tipos de recursão são tail recursion (chamada recursiva é última operação) e poderiam ser otimizados por compiladores que implementam tail call optimization (TCO). Python não realiza TCO, portanto tail recursion ainda consome frames de pilha e não evita o limite de profundidade.


Boas práticas e uso pedagógico

  1. Sempre identifique e documente o caso base. Sem ele, há erro conceitual.
  2. Desenhe a árvore de chamadas em sala (ou com um quadro) para visualizar empilhamento e desempilhamento.
  3. Compare versões recursiva e iterativa para o mesmo problema: ajuda a entender trade-offs.
  4. Use memoização quando pertinente e explique por que reduz complexidade (ex.: Fibonacci de O(ϕⁿ) → O(n)).
  5. Alerta prático: não incentive elevar indiscriminadamente recursionlimit; prefira algoritmos iterativos para grandes entradas ou trate de dividir o problema.

Exemplos clássicos

1) Cálculo do fatorial (recursivo)

Conceito: n! = n × (n-1)! com caso base 0! = 1. Recursão natural sobre n.

def fatorial(n):
    """
    Calcula n! de forma recursiva.
    Domínio: n inteiro não-negativo.
    Levanta ValueError para n < 0.
    """
    if not isinstance(n, int):
        raise TypeError("n deve ser inteiro")
    if n < 0:
        raise ValueError("Fatorial não está definido para n < 0")
    # Caso base
    if n == 0:
        return 1
    # Passo recursivo
    return n * fatorial(n - 1)

# Exemplos de uso
print(fatorial(0))  # 1
print(fatorial(5))  # 120

Complexidade: O(n) chamadas recursivas e O(n) multiplicações.
Observações: Python não possui otimização de cauda; para n muito grande, pode atingir o limite de recursão (RecursionError). Para n grande prefira bibliotecas ou algoritmos iterativos.


2) Soma dos elementos de uma lista (recursão cabeça/cauda)

Conceito: sum([x0, x1, x2,...]) = x0 + sum([x1,x2,...]). Exemplo pedagógico usando cabeça/cauda. Mostra a ideia, mas atenue para cópia ao usar slicing.

def soma_lista_head_tail(lista):
    """
    Soma de elementos por recursão cabeça/cauda (head/tail).
    Implementação pedagógica (usa slicing).
    """
    # Caso base: lista vazia -> soma 0
    if lista == []:
        return 0
    # cabeça = lista[0], cauda = lista[1:]
    cabeca = lista[0]
    cauda = lista[1:]
    return cabeca + soma_lista_head_tail(cauda)

# Exemplo pedagógico
print(soma_lista_head_tail([1, 2, 3, 4]))  # 10

Nota de eficiência: lista[1:] cria nova lista a cada chamada (O(n) por slicing), portanto complexidade total fica O(n²) em custo de cópias. Para versão eficiente, use índice:

def soma_lista_efficient(lista, i=0):
    """
    Soma de elementos por recursão usando índice para evitar slicing.
    Chamar soma_lista_efficient(lista) externamente.
    """
    if i >= len(lista):
        return 0
    return lista[i] + soma_lista_efficient(lista, i + 1)

print(soma_lista_efficient([1,2,3,4]))  # 10

Complexidade (versão com índice): O(n) tempo e O(n) profundidade de pilha.


3) Potência inteira por recursão rápida — exponenciação binária

Conceito: computar a**n com recursão que reduz n aproximadamente pela metade:

  • se n == 0 então 1;

  • se n par: a**n = (a**(n/2))**2;

  • se n ímpar: a**n = a * a**(n-1).

def pow_fast(a, n):
    """
    Potência inteira por recursão rápida (exponenciação binária).
    Domínio: n inteiro não-negativo.
    """
    if not isinstance(n, int):
        raise TypeError("expoente n deve ser inteiro")
    if n < 0:
        # opcional: tratar expoentes negativos convertendo para floats
        # aqui escolhemos tratar como erro para manter inteireza
        raise ValueError("expoente negativo não suportado nesta versão")
    if n == 0:
        return 1
    if n % 2 == 0:
        half = pow_fast(a, n // 2)
        return half * half
    else:
        return a * pow_fast(a, n - 1)

# Exemplos
print(pow_fast(2, 0))  # 1
print(pow_fast(2, 10)) # 1024
print(pow_fast(3, 5))  # 243

Complexidade: O(log n) multiplicações (profundidade recursiva ≈ log₂ n). Muito mais eficiente que a versão linear.

Observação: para expoentes negativos, pode retornar 1 / pow_fast(a, -n) usando floats — tratar com cuidado precisão.


4) Percorrer estruturas recursivas: árvore binária (pré-ordem / em-ordem / pós-ordem)

Conceito: Árvores binárias são recursivas por natureza: um nó tem subárvores esquerda e direita. As travessias recursivas definem ordens diferentes.

class Node:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

# Funções de travessia que retornam listas de valores

def preorder(node):
    """Pré-ordem: visita nó, depois esquerda, depois direita."""
    if node is None:
        return []
    return [node.value] + preorder(node.left) + preorder(node.right)

def inorder(node):
    """Em-ordem (in-order): esquerda, nó, direita."""
    if node is None:
        return []
    return inorder(node.left) + [node.value] + inorder(node.right)

def postorder(node):
    """Pós-ordem: esquerda, direita, nó."""
    if node is None:
        return []
    return postorder(node.left) + postorder(node.right) + [node.value]

# Exemplo de árvore:
#        A
#       / \
#      B   C
#     / \   \
#    D   E   F

tree = Node("A",
            Node("B",
                 Node("D"),
                 Node("E")),
            Node("C",
                 None,
                 Node("F")))

print("Pré-ordem:", preorder(tree))   # ['A','B','D','E','C','F']
print("Em-ordem:", inorder(tree))     # ['D','B','E','A','C','F']
print("Pós-ordem:", postorder(tree))  # ['D','E','B','F','C','A']

Complexidade: Cada travessia visita cada nó uma vez: O(n) tempo e O(h) profundidade de recursão (h = altura da árvore). Para árvores muito desbalanceadas, profundidade pode ser O(n).


5) Soma dos dígitos de um número (recursão sobre representação decimal)

Conceito: sum_digits(n) = n % 10 + sum_digits(n // 10) com caso base n == 0.

def soma_digitos(n):
    """
    Soma os dígitos de um inteiro n (absoluto).
    Ex.: soma_digitos(123) -> 6
    """
    if not isinstance(n, int):
        raise TypeError("n deve ser inteiro")
    n = abs(n)  # tratar números negativos
    if n < 10:
        return n
    return (n % 10) + soma_digitos(n // 10)

# Exemplos
print(soma_digitos(0))     # 0
print(soma_digitos(7))     # 7
print(soma_digitos(12345)) # 15
print(soma_digitos(-109))  # 10

Complexidade: O(d) onde d é o número de dígitos (≈ log₁₀ n).

Observação: Caso n == 0 retorna 0; caso base usado é n < 10.


6) Inversão de string por recursão (didático; não eficiente em produção)

Conceito: reverse(s) = last_char + reverse(s[:-1]) ou reverse(s) = reverse(s[1:]) + s[0]. Versão didática (usa slicing).

def reverse_string(s):
    """
    Inverte string por recursão (educacional).
    Observação: usa slicing e concatenação, não eficiente para strings grandes.
    """
    if not isinstance(s, str):
        raise TypeError("s deve ser string")
    # Caso base: string vazia ou um caractere
    if len(s) <= 1:
        return s
    # Passo recursivo: inverte a cauda e adiciona a cabeça no final
    return reverse_string(s[1:]) + s[0]

# Exemplos
print(reverse_string(""))        # ""
print(reverse_string("a"))       # "a"
print(reverse_string("Python"))  # "nohtyP"

Eficiência: Cada concatenação cria nova string; complexidade O(n²) no total. Em produção, prefira ''.join(reversed(s)) ou s[::-1].


Fim da aula 02

Click to listen highlighted text!