domingo, 22 de outubro de 2017

Exercícios de Programação Descendente (II)

Nas aulas foi colocado o problema de visualizar na forma de um histograma o resultado de uma experiência de lançamento de um dado. O histograma permitia saber quantas vezes saiu cada número. Pretende-se algo como a figura ilustra.

Vamos novamente tentar perceber se podemos dividir o problema em sub-problemas de modo a tornar a nossa missão mais fácil. Perante o anunciado é evidente que temos dois sub-problemas: (1) efectuar a experiência contando o número de vezes que saiu cada número e, (2) usar essa informação para construir o histograma. As condições do enunciado forçam a usar o modulo turtle para a visualização! É clara a existência de uma dependência entre os dois sub-problemas, pelo que antes de começarmos a resolver em separado cada um deles precisamos definir o seu interface. Uma opção que se impõe é que o sub-problema (1) depende do número de lançamentos n para construir um tuplo (n1,n2,n3,n4,n5,n6), com ni igual ao número de vezes que saiu o número i, e n1+n2+n3+n4+n5+n6 = n. A escolha de um tuplo é natural pois precisamos de um contentor, com a função de memória. Tomadas estas decisões, podemos passar a concretização do programa.
import turtle

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    pass


def histograma(res):
    pass


if __name__ == '__main__':
    n = 100
    dados_histo(n)
Vamos começar por resolver o segundo sub-problema. Para tal, vamos de novo decompor o sub-problema em sub-problemas. Aqui temos, pelo menos, duas opções: (a) numa leitura “vertical”, temos quatro sub-problemas: escreve os números de 1 a 6, desenha um traço, desenha os rectângulos e escreve os números correspondentes aos números de vezes que saiu cada número; (b) numa leitura “horizontal, temos seis sub-problemas idênticos: escrever um número, desenhar um traço, desenhar uma coluna e, novamente, escrever um número. A nossa escolha vai ser a segunda, pois é aquela que nos permite ter mais graus de liberdade.
import turtle

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    pass

def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha

def desenha(i,alt, posx,posy,comp_linha):
    pass

if __name__ == '__main__':
    n = 100
    teste = (46,39,105,0,44,5)
    histograma(teste)
    #dados_histo(n)
No esboço de solução apresentado podemos verificar que o programa histograma se limita a desenhar cada caso em sequência. Note como conseguimos os valores do número e do número de vezes que saiu graça ao uso de enumerate. Note ainda que a opção tomada obriga a que cada caso singular tenha que saber as quatro componentes relevantes: posição, tamanho do traço, numero do dado e numero de vezes que saiu. A posição para desenhar a coluna vai ser o centro pelo que pode ser calculada a partir do conhecimento do tamanho do traço. Tal como está, podemos testar o programa … mesmo que este não faça nada! Esta é uma das vantagens da programação descendente: podemos testar primeiro as soluções para os problemas e depois de as integrarmos no programa principal, testar o programa principal. A eliminação de eventuais erros é deste modo mais fácil de fazer. Passemos ao caso mais básico. São quatro so sub-problemas básicos que o compõem: número, linha, coluna, número. No entanto os dois problema de escrita de um número são na realidade o mesmo.
def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i   
    # desenha linha
    # desenha coluna
    # escreve número de vezes (alt) que saiu o número i  
    pass
Podemos resolver cada um destes três sub-problemas e testá-los isoladamente.
def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.color(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
Agora precisamos de integrar esses sub-problemas no sub-problema de desenho de uma componente. Aqui vamos ter que perceber as relações entre cada um dos sub-componentes. Em primeiro lugar, decidimos que a largura da coluna será igual a metade do comprimento do traço. Em segundo lugar, fixamos a fonte no tamanho 12 e controlamos a posição da escrita.
def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha linha
    linha(posx,posy,comp_linha,'red')
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')
Podemos agora testar o problema de visualizar. E está na hora de ver tudo junto.
import turtle

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    pass


def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha

def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha linha
    linha(posx,posy,comp_linha,'red')
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')


def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.color(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
  
    
    
    
if __name__ == '__main__':
    n = 100
    teste = (46,39,105,0,44,5)
    fonte = ('Arial', 24, 'bold')
    posx = 0
    posy = 0 
    cor_1 = 'black'
    cor_2 = 'red'
    comp = 80
    lado_1 = comp/2
    lado_2 = 50
    #escreve_numero(posx,posy,fonte,n,cor)
    #linha(posx,posy,comp,cor_2)
    #coluna(posx,posy, lado_1, lado_2,cor_2)
    #desenha(4,100, posx,posy,comp)
    histograma(teste)
    #dados_histo(n)
    turtle.exitonclick()
Agora é a vez do primeiro sub-problema, simular o lançamento do dado. Já fizemos isso em problemas anteriores semelhantes. Aqui a novidade reside no facto de querermos memorizar os resultados. Dado o facto de estarmos a usar tuplos, que são objectos imutáveis, vamos decompor esta questão em duas: (1) guardar os valores saídos em cada lançamento, (2) contar quantas vezes saiu cada um. Solução óbvia:
def lanca_dado(n):
    # lançamento
    resultado = tuple()
    for i in range(n):
        numero = random.randint(1,6)
        resultado = resultado + (numero,)
    # contagem
    conta = tuple()
    for i in range(1,7):
        conta_i = resultado.count(i)
        conta = conta + (conta_i,)
    return conta
Podemos finalmente testar o programa completo.
import turtle
import random

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    # lançamento
    resultado = tuple()
    for i in range(n):
        numero = random.randint(1,6)
        resultado = resultado + (numero,)
    # contagem
    conta = tuple()
    for i in range(1,7):
        conta_i = resultado.count(i)
        conta = conta + (conta_i,)
    return conta


def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha

def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha linha
    linha(posx,posy,comp_linha,'red')
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')


def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.color(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
  
    
    
    
if __name__ == '__main__':
    n = 100
    teste = (46,39,105,0,44,5)
    fonte = ('Arial', 24, 'bold')
    posx = 0
    posy = 0 
    cor_1 = 'black'
    cor_2 = 'red'
    comp = 80
    lado_1 = comp/2
    lado_2 = 50
    #escreve_numero(posx,posy,fonte,n,cor)
    #linha(posx,posy,comp,cor_2)
    #coluna(posx,posy, lado_1, lado_2,cor_2)
    #desenha(4,100, posx,posy,comp)
    #histograma(teste)
    #print(lanca_dado(n))
    dados_histo(n)
    turtle.exitonclick()
    
    
Para concluir o exercício, experimente com diferentes valores de tentativas. Que conclusões pode tirar à medida que n aumenta?? Identifique os pontos em que a solução não é genérica. como pode alterar a situação??

Na sala, alguns disseram que nos histogramas as colunas não estão separadas. A adaptação do código feito para que a visualização seja essa é mínima: retirar a linha na definição desenha, separar a cor do traço (pencolor) da cor de preenchimento (fillcolor) em desenha para que as colunas fiquem claramente a ver-se e, na função histograma, alterar o posicionamento ao longo do eixo dos xx da cada coluna.
import turtle
import random

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    # lançamento
    resultado = tuple()
    for i in range(n):
        numero = random.randint(1,6)
        resultado = resultado + (numero,)
    # contagem
    conta = tuple()
    for i in range(1,7):
        conta_i = resultado.count(i)
        conta = conta + (conta_i,)
    return conta


def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha/2

def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')


def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.pencolor('black')
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
  
    
    
    
if __name__ == '__main__':
    n = 1000
    teste = (46,39,105,0,44,5)
    fonte = ('Arial', 24, 'bold')
    posx = 0
    posy = 0 
    cor_1 = 'black'
    cor_2 = 'red'
    comp = 80
    lado_1 = comp/2
    lado_2 = 50
    #escreve_numero(posx,posy,fonte,n,cor)
    #linha(posx,posy,comp,cor_2)
    #coluna(posx,posy, lado_1, lado_2,cor_2)
    #desenha(4,100, posx,posy,comp)
    #histograma(teste)
    #print(lanca_dado(n))
    dados_histo(n)
    turtle.exitonclick()
    
Para o leitor: e se quisermos que o histograma possa ter uma orientação qualquer??

Sem comentários:

Enviar um comentário