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:

  1. Receba uma lista de números inteiros positivos e negativos.
  2. 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)

  1. Salve o arquivo como: Atividade_01_1_SeuNome.py
  2. 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:

  1. Implemente o algoritmo de reversão de vetor utilizando a técnica de dois ponteiros (left e right).
  2. 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)

  1. Salve o arquivo como: Atividade_01_2_SeuNome.py
  2. 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:

  1. Implemente um simulador imperativo de Call Stack (PilhaExecucao).
  2. Cada registro de ativação (Stack Frame) deve armazenar o nome da função, endereço de retorno e tabela de símbolos locais.
  3. 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)

  1. Salve o arquivo como: Atividade_01_3_SeuNome.py
  2. 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:

  1. Crie um MemoryHeapAllocator com capacidade fixa de $1024\text{ bytes}$.
  2. Implemente o método malloc(size_bytes: int) -> int que retorna o ponteiro/endereço inicial alocado usando a estratégia First-Fit.
  3. Implemente o método free(pointer_address: int) -> bool que 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)

  1. Salve o arquivo como: Atividade_01_4_SeuNome.py
  2. 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.")