🧩 Exercícios: Paradigma Imperativo e Estruturado
Módulo 10: Paradigmas & Padrões • Bloco 1 • Trilha em 4 Níveis
🎯 Nível 1: Fundamentos
Problema 01.1 — Acumuladores e Controle de Fluxo Manual
Contexto: O paradigma imperativo fundamenta-se na manipulação explícita de variáveis de estado na memória RAM e controle sequencial.
Requisitos de Execução:
- Receba uma lista de números inteiros positivos e negativos.
- Utilizando apenas laços iterativos clássicos (
for/while) e variáveis acumuladoras manuais, calcule:- A soma dos números positivos;
- A quantidade de números negativos;
- O maior número par encontrado.
Resultado Esperado
Entrada: [14, -5, 22, -9, 31, -2, 8]
Soma dos Positivos: 75
Quantidade de Negativos: 3
Maior Par Encontrado: 22
📤 Instruções de Entrega (Microsoft Teams)
- Salve o arquivo como:
Atividade_01_1_SeuNome.py - Envie na tarefa:
Atividade Cap 01 - Paradigma Imperativo e Estruturado
🔑 Gabarito de Código & Solução Comentada
# Atividade_01_1_Gabarito.py
def processar_metricas_imperativas(numeros: list) -> dict:
soma_positivos = 0
qtd_negativos = 0
maior_par = None
for n in numeros:
# Acumulador condicional
if n > 0:
soma_positivos += n
elif n < 0:
qtd_negativos += 1
# Rastreamento de maior par
if n % 2 == 0:
if maior_par is None or n > maior_par:
maior_par = n
return {
"soma_positivos": soma_positivos,
"qtd_negativos": qtd_negativos,
"maior_par": maior_par
}
if __name__ == "__main__":
dados = [14, -5, 22, -9, 31, -2, 8]
res = processar_metricas_imperativas(dados)
print(f"Entrada: {dados}")
print(f"Soma dos Positivos: {res['soma_positivos']}")
print(f"Quantidade de Negativos: {res['qtd_negativos']}")
print(f"Maior Par Encontrado: {res['maior_par']}")
🔍 Nível 2: Prática
Problema 01.2 — Reversão de Lista In-Place e Ponteiros de Dois Extremos (Two Pointers)
Contexto: No paradigma imperativo, a otimização de memória visa alterar estruturas diretamente (in-place) sem alocar cópias adicionais no Heap.
Requisitos de Execução:
- Implemente o algoritmo de reversão de vetor utilizando a técnica de dois ponteiros (
lefteright). - Troque os elementos de posição em memória utilizando uma variável auxiliar temporária sem usar métodos de conveniência prontos (como
.reverse()ou fatiamento[::-1]).
Resultado Esperado
Array Original: ['A', 'B', 'C', 'D', 'E']
Array Revertido In-Place: ['E', 'D', 'C', 'B', 'A']
Memória: O vetor foi modificado diretamente no mesmo endereço!
📤 Instruções de Entrega (Microsoft Teams)
- Salve o arquivo como:
Atividade_01_2_SeuNome.py - Envie na tarefa:
Atividade Cap 01 - Paradigma Imperativo e Estruturado
🔑 Gabarito de Código & Solução Comentada
# Atividade_01_2_Gabarito.py
def inverter_in_place(arr: list) -> None:
left = 0
right = len(arr) - 1
while left < right:
# Troca clássica imperativa com variável temporária
temp = arr[left]
arr[left] = arr[right]
arr[right] = temp
left += 1
right -= 1
if __name__ == "__main__":
dados = ['A', 'B', 'C', 'D', 'E']
id_orig = id(dados)
print(f"Array Original: {dados}")
inverter_in_place(dados)
print(f"Array Revertido In-Place: {dados}")
assert id(dados) == id_orig
print("Memória: O vetor foi modificado diretamente no mesmo endereço!")
🚀 Nível 3: Integração
Problema 01.3 — Máquina de Pilha e Simulação de Call Stack
Contexto: As linguagens imperativas utilizam a estrutura de Call Stack (LIFO) para controlar chamadas de procedimentos aninhadas e escopo de variáveis.
Requisitos de Execução:
- Implemente um simulador imperativo de Call Stack (
PilhaExecucao). - Cada registro de ativação (Stack Frame) deve armazenar o nome da função, endereço de retorno e tabela de símbolos locais.
- Simule a execução aninhada:
main()$\rightarrow$calcular_imposto()$\rightarrow$obter_taxa_banco().
Resultado Esperado
[CALL] Empilhando Frame: main() (Profundidade: 1)
[CALL] Empilhando Frame: calcular_imposto() (Profundidade: 2)
[CALL] Empilhando Frame: obter_taxa_banco() (Profundidade: 3)
[RET] Desempilhando: obter_taxa_banco() -> Retorno: 0.15
[RET] Desempilhando: calcular_imposto() -> Retorno: R$ 150.00
[RET] Desempilhando: main() -> Concluído
📤 Instruções de Entrega (Microsoft Teams)
- Salve o arquivo como:
Atividade_01_3_SeuNome.py - Envie na tarefa:
Atividade Cap 01 - Paradigma Imperativo e Estruturado
🔑 Gabarito de Código & Solução Comentada
# Atividade_01_3_Gabarito.py
from dataclasses import dataclass
from typing import Any, Dict, List
@dataclass
class StackFrame:
funcao: str
variaveis_locais: Dict[str, Any]
class CallStackSimulator:
def __init__(self):
self._stack: List[StackFrame] = []
def call(self, nome_funcao: str, params: dict):
frame = StackFrame(nome_funcao, params)
self._stack.append(frame)
print(f"[CALL] Empilhando Frame: {nome_funcao}() (Profundidade: {len(self._stack)})")
def ret(self, valor_retorno: Any):
if not self._stack:
raise IndexError("Stack Underflow")
frame = self._stack.pop()
print(f"[RET] Desempilhando: {frame.funcao}() -> Retorno: {valor_retorno}")
if __name__ == "__main__":
stack = CallStackSimulator()
stack.call("main", {"args": []})
stack.call("calcular_imposto", {"valor": 1000.0})
stack.call("obter_taxa_banco", {"tipo": "ICMS"})
stack.ret("0.15")
stack.ret("R$ 150.00")
stack.ret("Concluído")
🏆 Nível 4: Desafio
Problema 01.4 — Simulador de Alocador de Memória Dinâmica (Heap Allocator)
Contexto: No desenvolvimento de software de sistemas (como drivers e sistemas operacionais), o desenvolvedor deve gerenciar manualmente blocos contíguos de memória, fragmentação e liberação (malloc e free).
Requisitos de Execução:
- Crie um
MemoryHeapAllocatorcom capacidade fixa de $1024\text{ bytes}$. - Implemente o método
malloc(size_bytes: int) -> intque retorna o ponteiro/endereço inicial alocado usando a estratégia First-Fit. - Implemente o método
free(pointer_address: int) -> boolque marca o bloco como livre e funde (coalesce) blocos livres adjacentes para evitar fragmentação externa.
Resultado Esperado
=== Simulador Heap Allocator (1024 Bytes) ===
[MALLOC] Alocados 120 bytes no endereço #0
[MALLOC] Alocados 200 bytes no endereço #120
[MALLOC] Alocados 80 bytes no endereço #320
[FREE] Liberado bloco no endereço #120 (200 bytes)
[MALLOC] Alocados 150 bytes no endereço #120 (Reaproveitamento de bloco livre!)
Fragmentação total controlada com sucesso.
📤 Instruções de Entrega (Microsoft Teams)
- Salve o arquivo como:
Atividade_01_4_SeuNome.py - Envie na tarefa:
Atividade Cap 01 - Paradigma Imperativo e Estruturado
🔑 Gabarito de Código & Solução Comentada
# Atividade_01_4_Gabarito.py
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class BlocoMemoria:
endereco: int
tamanho: int
ocupado: bool
class MemoryHeapAllocator:
def __init__(self, capacidade_total: int = 1024):
self.capacidade = capacidade_total
self.blocos: List[BlocoMemoria] = [BlocoMemoria(0, capacidade_total, False)]
def malloc(self, size_bytes: int) -> Optional[int]:
for i, bloco in enumerate(self.blocos):
if not bloco.ocupado and bloco.tamanho >= size_bytes:
sobra = bloco.tamanho - size_bytes
bloco.tamanho = size_bytes
bloco.ocupado = True
addr = bloco.endereco
if sobra > 0:
novo_bloco = BlocoMemoria(addr + size_bytes, sobra, False)
self.blocos.insert(i + 1, novo_bloco)
print(f"[MALLOC] Alocados {size_bytes} bytes no endereço #{addr}")
return addr
print(f"[OUT OF MEMORY] Não há bloco contíguo de {size_bytes} bytes.")
return None
def free(self, endereco: int) -> bool:
for i, bloco in enumerate(self.blocos):
if bloco.endereco == endereco and bloco.ocupado:
bloco.ocupado = False
print(f"[FREE] Liberado bloco no endereço #{endereco} ({bloco.tamanho} bytes)")
self._fundir_adjacentes()
return True
return False
def _fundir_adjacentes(self):
i = 0
while i < len(self.blocos) - 1:
atual = self.blocos[i]
proximo = self.blocos[i + 1]
if not atual.ocupado and not proximo.ocupado:
atual.tamanho += proximo.tamanho
self.blocos.pop(i + 1)
else:
i += 1
if __name__ == "__main__":
print("=== Simulador Heap Allocator (1024 Bytes) ===")
heap = MemoryHeapAllocator(1024)
p1 = heap.malloc(120)
p2 = heap.malloc(200)
p3 = heap.malloc(80)
heap.free(p2)
p4 = heap.malloc(150)
print("Fragmentação total controlada com sucesso.")