MC-102 — Aula 15
Decomposicao de problemas

Quando o ChatGPT nao consegue resolver o seu problema de uma vez só.

Estrutura tipica

dados = le()
saida = processa(dados)
imprime(saida)

variacoes:

leituras complicadas - quebrando em 2 ou mais

dados1 = le1()
dados2 = le2()
saida = processa(dados1, dados2)
imprime(saida)

processamento complicado, quebrando em varios sequenciais

dados = le()
resultados1 = processa1(dados)
resultados2 = processa2(resultados1)
imprime(resultados2)

loop para ler dados novos e processa-los

dados_gerais = legeral()
dados_novos = le_novos()
while tem_dados(dados_novos):
    resultado = processa(dados_gerais, dados_novos)
    imprime(resultado)
    dados_novos = le_novos()

O problema da cerveja da primeira aula

http://www.highwoodsbrewing.com/ibu.php

1 leitura

escreva uma funcao que le numa linha de dados, o IBU, o alpha acid, o volume, o biol gravity e o boil time. Retorne uma tupla com esses valores
def ler_dados(linha):
    # Divide a linha em partes usando espaços como delimitadores
    partes = linha.split()
    
    # Converte cada parte para o tipo apropriado
    ibu = float(partes[0])
    alpha_acid = float(partes[1])
    volume = float(partes[2])
    boil_gravity = float(partes[3])
    boil_time = int(partes[4])
    
    # Retorna uma tupla com os valores
    return (ibu, alpha_acid, volume, boil_gravity, boil_time)
    
ibu,aa,vol,bg,bt = ler_dados()

2) le a tabela

a pagina   http://www.highwoodsbrewing.com/ibu.php contem uma tabela (a primeira tabele). Leia a pagina e retorne a tabela como uma matrix de numeros

resposta

import requests
from bs4 import BeautifulSoup

def extrair_tabela_como_matriz(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')

    # Encontrar a primeira tabela
    table = soup.find('table')
    matriz = []

    # Iterar sobre as linhas da tabela
    for row in table.find_all('tr'):
        linha = []
        # Iterar sobre as colunas da linha
        for cell in row.find_all(['td', 'th']):
            # Converter o texto da célula para float se possível
            try:
                linha.append(float(cell.get_text(strip=True)))
            except ValueError:
                linha.append(cell.get_text(strip=True))
        matriz.append(linha)
    
    return matriz

# URL da página
url = "http://www.highwoodsbrewing.com/ibu.php"
tabela = extrair_tabela_como_matriz(url)

Ele retorna uma lista de linhas como a matriz!!

no programa acima, o primeiro elemento da primeira linha nao é um numero. é um string. subsitutia esse string por algum numero e retorne uma matriz numpy
def extrair_tabela_como_matriz(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')

    # Encontrar a primeira tabela
    table = soup.find('table')
    matriz = []

    # Iterar sobre as linhas da tabela
    for row in table.find_all('tr'):
        linha = []
        # Iterar sobre as colunas da linha
        for cell in row.find_all(['td', 'th']):
            # Converter o texto da célula para float se possível
            try:
                linha.append(float(cell.get_text(strip=True)))
            except ValueError:
                linha.append(cell.get_text(strip=True))
        matriz.append(linha)
    
    return matriz

testar

Buscar o U na tabela

Dado uma matriz numpy onde a primeira linha contem numeros que indicam o gravity boil das colunas correspondentes, e a primeira coluna contem os numeros que indicam o boil time das linhas correspondentes. Escreva uma funcao que dado a matriz descrita, o valor do boil gravity e o valor do boil time retorna o valor correspondente a essa coluna e linha da matriz
import numpy as np

def obter_valor_correspondente(matriz, boil_gravity, boil_time):
    # Extrair a primeira linha e a primeira coluna
    primeira_linha = matriz[0, :]
    primeira_coluna = matriz[:, 0]

    # Encontrar os índices correspondentes
    try:
        indice_coluna = np.where(primeira_linha == boil_gravity)[0][0]
        indice_linha = np.where(primeira_coluna == boil_time)[0][0]
    except IndexError:
        raise ValueError("Boil gravity ou boil time não encontrados na matriz.")

    # Retornar o valor correspondente na matriz
    return matriz[indice_linha, indice_coluna]

# Exemplo de uso
matriz = np.array([
    [0, 1.030, 1.040, 1.050],
    [30, 20.0, 30.0, 40.0],
    [60, 25.0, 35.0, 45.0],
    [90, 30.0, 40.0, 50.0]
])

boil_gravity = 1.040
boil_time = 60
valor = obter_valor_correspondente(matriz, boil_gravity, boil_time)

testar

4) testar ate aqui

ibu,aa,vol,bg,bt = ler_dados()
url = "http://www.highwoodsbrewing.com/ibu.php"
tabela = extrair_tabela_como_matriz(url)
U = obter_valor_correspondente(tabela, bg, bt)

5) fazer as contas finais e imprimir

6) E se o valor do gravity boil e/ou do boil time nao estiverem na table?

A palavra é interpolar.

Dado uma matriz numpy onde a primeira linha contem numeros que indicam o gravity boil das colunas correspondentes, e a primeira coluna contem os numeros que indicam o boil time das linhas correspondentes. O gravity boil é crescente da esquerda para a direira e o bol time é crescente de cima para baixo na matriz. Escreva uma funcao que dado a matriz descrita, o valor do boil gravity e o valor do boil time retorna o valor correspondente a essa coluna e linha da matriz. Se o valor do gravity boil e/ou do boil time nao estiverem na matriz, faca a interpolaçao aproproada
import numpy as np

def find_nearest(array, value):
    array = np.asarray(array)
    idx = (np.abs(array - value)).argmin()
    return idx

def interpolate(x0, x1, y0, y1, x):
    return y0 + (y1 - y0) * (x - x0) / (x1 - x0)

def get_value_from_matrix(matrix, gravity_boil, boil_time):
    # Extrair os valores de gravity boil (primeira linha) e boil time (primeira coluna)
    gravity_boil_values = matrix[0, 1:]
    boil_time_values = matrix[1:, 0]
    data_matrix = matrix[1:, 1:]

    # Encontrar os índices mais próximos para gravity_boil e boil_time
    gravity_boil_idx = find_nearest(gravity_boil_values, gravity_boil)
    boil_time_idx = find_nearest(boil_time_values, boil_time)

    # Verificar se os valores estão exatamente na matriz
    exact_gravity = gravity_boil_values[gravity_boil_idx] == gravity_boil
    exact_boil_time = boil_time_values[boil_time_idx] == boil_time

    if exact_gravity and exact_boil_time:
        return data_matrix[boil_time_idx, gravity_boil_idx]

    # Interpolação para gravity_boil
    if not exact_gravity:
        if gravity_boil < gravity_boil_values[0]:
            gravity_boil_idx = 0
        elif gravity_boil > gravity_boil_values[-1]:
            gravity_boil_idx = len(gravity_boil_values) - 1
        else:
            if gravity_boil > gravity_boil_values[gravity_boil_idx]:
                gravity_boil_idx1 = gravity_boil_idx
                gravity_boil_idx2 = gravity_boil_idx + 1
            else:
                gravity_boil_idx1 = gravity_boil_idx - 1
                gravity_boil_idx2 = gravity_boil_idx
            gravity_boil1 = gravity_boil_values[gravity_boil_idx1]
            gravity_boil2 = gravity_boil_values[gravity_boil_idx2]

    # Interpolação para boil_time
    if not exact_boil_time:
        if boil_time < boil_time_values[0]:
            boil_time_idx = 0
        elif boil_time > boil_time_values[-1]:
            boil_time_idx = len(boil_time_values) - 1
        else:
            if boil_time > boil_time_values[boil_time_idx]:
                boil_time_idx1 = boil_time_idx
                boil_time_idx2 = boil_time_idx + 1
            else:
                boil_time_idx1 = boil_time_idx - 1
                boil_time_idx2 = boil_time_idx
            boil_time1 = boil_time_values[boil_time_idx1]
            boil_time2 = boil_time_values[boil_time_idx2]

    # Se ambos os valores precisam de interpolação
    if not exact_gravity and not exact_boil_time:
        z11 = data_matrix[boil_time_idx1, gravity_boil_idx1]
        z12 = data_matrix[boil_time_idx1, gravity_boil_idx2]
        z21 = data_matrix[boil_time_idx2, gravity_boil_idx1]
        z22 = data_matrix[boil_time_idx2, gravity_boil_idx2]

        z1 = interpolate(gravity_boil1, gravity_boil2, z11, z12, gravity_boil)
        z2 = interpolate(gravity_boil1, gravity_boil2, z21, z22, gravity_boil)

        result = interpolate(boil_time1

Cupons (da semana retrasada)

  1. Há algum truque para computar a solucao?

Eu acho que não.

Mas se os cupons fossem do tipo, “ganhe x% de desconte nesta compra” (desconto fixo e sem limite) ai havera uma solucao simples. Pegue o cupom com maior desconto e use-o na maior compra, o proximo maior desconto na 2a maior compra e assim por diante.

Mas no caso de cupons com limite no desconto, nao esta claro (para mim) que haja algum truque simples. Por que? Porque um cupom com desconto maior mas com limite menor pode ser pior (dependendo da compra) que um com desconto menor mais limite maior.

  1. Se não ha truque, temos que tentar todas as combinacoes de compras e cupons e escolher a com maior desconto total. Tem essa historia de ordem se houver empates. vamos tratar disso depois.
dados = le()
compras = gera_compras(dados)
cupons = gera_cupons(dados)
combinacoes = gera_combinacoes(compras, cupons)
descontos = []
for c in combinacoes:
    x = computa_desconto_total(c)
    descontos.append(x)
ordenado = sorted(descontos, reverse=True)
print(ordenado[0])

1) Leitura

dado essa especificacao:

Como entrada, o seu programa deve receber 9 linhas com os valores de Q1, X1, Z1, Q2, X2, Z2, Q3, X3 e Z3, nessa ordem. Após isso, o seu programa deve receber uma linha com o número de compras n, seguido de n linhas com números que indicam o valor de cada uma dessas n compras. O i-ésimo valor lido indica a compra de índice i.

escreva uma funcao que le os dados e retorna uma tupla com os valores lidos de Q1 a Z3, e uma lista dos valores das compras. Leia os dados do arquivo a.txt
def ler_entrada_arquivo(caminho_arquivo):
    with open(caminho_arquivo, 'r') as file:
        linhas = file.readlines()
    
    # Ler os valores de Q1, X1, Z1, Q2, X2, Z2, Q3, X3 e Z3
    Q1 = int(linhas[0].strip())
    X1 = float(linhas[1].strip())
    Z1 = float(linhas[2].strip())
    Q2 = int(linhas[3].strip())
    X2 = float(linhas[4].strip())
    Z2 = float(linhas[5].strip())
    Q3 = int(linhas[6].strip())
    X3 = float(linhas[7].strip())
    Z3 = float(linhas[8].strip())
    
    # Ler o número de compras
    n = int(linhas[9].strip())
    
    # Ler os valores das compras
    compras = []
    for i in range(10, 10 + n):
        compras.append(float(linhas[i].strip()))
    
    # Retornar uma tupla com os valores lidos e a lista de compras
    return (Q1, X1, Z1, Q2, X2, Z2, Q3, X3, Z3), compras

2) gera combinacoes

Dado uma lista de numeros e uma lista de stings. vamos chamar de uma atribuicao uma lista de tuplas onde o primeiro componente vem da 1a lista e o segundo componente vem da segunda lista. Dado as 2 listas mencionadas, gere uma funcao que gera uma lista com todos as possivies atribuicoes entre essas 2 listas
from itertools import product

def generate_assignments(numbers, strings):
    return list(product(numbers, strings))

# Exemplo de uso
numbers = [1, 2, 3]
strings = ['a', 'b', 'c']

assignments = generate_assignments(numbers, strings)
for assignment in assignments:
    print(assignment)
Eu nao fui claro. Uma atribuicao é uma lista de todas as combinacoes entre numero e strings, onde cada numeor e cada string so aparece em uma tupla. Eu quero a lista de todas aas atribuicoes. Ou seja uma lista de lista de tuplas
from itertools import permutations

def generate_all_assignments(numbers, strings):
    if len(numbers) != len(strings):
        raise ValueError("The two lists must have the same length")
    
    all_assignments = []
    for perm in permutations(strings):
        assignment = list(zip(numbers, perm))
        all_assignments.append(assignment)
    
    return all_assignments

# Exemplo de uso
numbers = [1, 2, 3]
strings = ['a', 'b', 'c']

assignments = generate_all_assignments(numbers, strings)
for assignment in assignments:
    print(assignment)
    
Esta solucao esta melhor. mas a lista de numeros e de strings NAO necessariamente precisam ter o mesmo tamanho. Por favor redefina a funcao de tal forma que as duas listas nao tenham que ser do mesmo tamanho 
from itertools import permutations, combinations

def generate_all_assignments(numbers, strings):
    if len(numbers) > len(strings):
        all_combinations = combinations(numbers, len(strings))
        all_assignments = []
        for comb in all_combinations:
            for perm in permutations(comb):
                assignment = list(zip(perm, strings))
                all_assignments.append(assignment)
    else:
        all_combinations = combinations(strings, len(numbers))
        all_assignments = []
        for comb in all_combinations:
            for perm in permutations(comb):
                assignment = list(zip(numbers, perm))
                all_assignments.append(assignment)
    
    return all_assignments

# Exemplo de uso
numbers = [1, 2, 3, 4]
strings = ['a', 'b']

assignments = generate_all_assignments(numbers, strings)
for assignment in assignments:
    print(assignment)

3) computar o desconto

OK. Na minha opiniao a parte dificil ja foi resolvida - gerar todas as combinacoes entre compras e cupons. Para calcular o desconto total, eu tenho que olhar para cada par compra/cupom e somar todos os descontos de uma combinacao.

def computa_desconto_total(c):
    total = 0.0
    for compra, cupon in c:
        desc = desconto(compra, cupon)
        total = total + desc
    return total

Finalmente para computar o desconto de uma compra com um cupom eu tenho que saber o que é uma compra (como ela esta representada) e o que é um cupom (como ele esta representado)

Como no final nos precisamos associar uma compra pela ordem e nao pelo valor com um cupom acho que é mais interessante que a compra seja um indice para a lista de valores. O cupom pode ser a tupla direto.