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.
01.import turtle
02. 
03.def dados_histo(n):
04.    # experiência de lançamentos
05.    res = lanca_dado(n)
06.    # visualizaçao
07.    histograma(res)
08.     
09.     
10.def lanca_dado(n):
11.    pass
12. 
13. 
14.def histograma(res):
15.    pass
16. 
17. 
18.if __name__ == '__main__':
19.    n = 100
20.    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.
01.import turtle
02. 
03.def dados_histo(n):
04.    # experiência de lançamentos
05.    res = lanca_dado(n)
06.    # visualizaçao
07.    histograma(res)
08.     
09.     
10.def lanca_dado(n):
11.    pass
12. 
13.def histograma(res):
14.    posx = -100
15.    posy = 0
16.    comp_linha = 80
17.    for i,alt in enumerate(res):
18.        # desenha caso i
19.        desenha(i+1,alt,posx,posy,comp_linha)
20.        # define parâmetros
21.        posx = posx + comp_linha
22. 
23.def desenha(i,alt, posx,posy,comp_linha):
24.    pass
25. 
26.if __name__ == '__main__':
27.    n = 100
28.    teste = (46,39,105,0,44,5)
29.    histograma(teste)
30.    #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.
1.def desenha(i,alt, posx,posy,comp_linha):
2.    # escreve número i  
3.    # desenha linha
4.    # desenha coluna
5.    # escreve número de vezes (alt) que saiu o número i 
6.    pass
Podemos resolver cada um destes três sub-problemas e testá-los isoladamente.
01.def coluna(posx,posy, lado_1, lado_2,cor):
02.    # posiciona
03.    turtle.penup()
04.    turtle.goto(posx,posy)
05.    turtle.pendown()
06.    # desenha
07.    turtle.color(cor)
08.    turtle.begin_fill()
09.    for i in range(2):
10.        turtle.forward(lado_1)
11.        turtle.left(90)
12.        turtle.forward(lado_2)
13.        turtle.left(90)
14.    turtle.end_fill()
15.    turtle.hideturtle()
16.     
17.def linha(posx,posy,comp_linha,cor):
18.    # posiciona
19.    turtle.penup()
20.    turtle.goto(posx,posy)
21.    turtle.color(cor)
22.    turtle.pendown()
23.    # linha
24.    turtle.forward(comp_linha)
25.    turtle.hideturtle()
26.     
27.def escreve_numero(posx,posy,fonte,valor,cor):
28.    # posiciona
29.    turtle.penup()
30.    turtle.goto(posx,posy)
31.    turtle.color(cor) 
32.    turtle.pendown()
33.    # escreve
34.    turtle.write(valor,font=fonte)
35.    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.
01.def desenha(i,alt, posx,posy,comp_linha):
02.    # escreve número i
03.    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')   
04.    # desenha linha
05.    linha(posx,posy,comp_linha,'red')
06.    # desenha coluna
07.    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
08.    # escreve número de vezes (alt) que saiu o número i 
09.    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.
01.import turtle
02. 
03.def dados_histo(n):
04.    # experiência de lançamentos
05.    res = lanca_dado(n)
06.    # visualizaçao
07.    histograma(res)
08.     
09.     
10.def lanca_dado(n):
11.    pass
12. 
13. 
14.def histograma(res):
15.    posx = -100
16.    posy = 0
17.    comp_linha = 80
18.    for i,alt in enumerate(res):
19.        # desenha caso i
20.        desenha(i+1,alt,posx,posy,comp_linha)
21.        # define parâmetros
22.        posx = posx + comp_linha
23. 
24.def desenha(i,alt, posx,posy,comp_linha):
25.    # escreve número i
26.    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')   
27.    # desenha linha
28.    linha(posx,posy,comp_linha,'red')
29.    # desenha coluna
30.    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
31.    # escreve número de vezes (alt) que saiu o número i 
32.    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')
33. 
34. 
35.def coluna(posx,posy, lado_1, lado_2,cor):
36.    # posiciona
37.    turtle.penup()
38.    turtle.goto(posx,posy)
39.    turtle.pendown()
40.    # desenha
41.    turtle.color(cor)
42.    turtle.begin_fill()
43.    for i in range(2):
44.        turtle.forward(lado_1)
45.        turtle.left(90)
46.        turtle.forward(lado_2)
47.        turtle.left(90)
48.    turtle.end_fill()
49.    turtle.hideturtle()
50.     
51.def linha(posx,posy,comp_linha,cor):
52.    # posiciona
53.    turtle.penup()
54.    turtle.goto(posx,posy)
55.    turtle.color(cor)
56.    turtle.pendown()
57.    # linha
58.    turtle.forward(comp_linha)
59.    turtle.hideturtle()
60.     
61.def escreve_numero(posx,posy,fonte,valor,cor):
62.    # posiciona
63.    turtle.penup()
64.    turtle.goto(posx,posy)
65.    turtle.color(cor) 
66.    turtle.pendown()
67.    # escreve
68.    turtle.write(valor,font=fonte)
69.    turtle.hideturtle()
70.   
71.     
72.     
73.     
74.if __name__ == '__main__':
75.    n = 100
76.    teste = (46,39,105,0,44,5)
77.    fonte = ('Arial', 24, 'bold')
78.    posx = 0
79.    posy = 0
80.    cor_1 = 'black'
81.    cor_2 = 'red'
82.    comp = 80
83.    lado_1 = comp/2
84.    lado_2 = 50
85.    #escreve_numero(posx,posy,fonte,n,cor)
86.    #linha(posx,posy,comp,cor_2)
87.    #coluna(posx,posy, lado_1, lado_2,cor_2)
88.    #desenha(4,100, posx,posy,comp)
89.    histograma(teste)
90.    #dados_histo(n)
91.    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:
01.def lanca_dado(n):
02.    # lançamento
03.    resultado = tuple()
04.    for i in range(n):
05.        numero = random.randint(1,6)
06.        resultado = resultado + (numero,)
07.    # contagem
08.    conta = tuple()
09.    for i in range(1,7):
10.        conta_i = resultado.count(i)
11.        conta = conta + (conta_i,)
12.    return conta
Podemos finalmente testar o programa completo.
001.import turtle
002.import random
003. 
004.def dados_histo(n):
005.    # experiência de lançamentos
006.    res = lanca_dado(n)
007.    # visualizaçao
008.    histograma(res)
009.     
010.     
011.def lanca_dado(n):
012.    # lançamento
013.    resultado = tuple()
014.    for i in range(n):
015.        numero = random.randint(1,6)
016.        resultado = resultado + (numero,)
017.    # contagem
018.    conta = tuple()
019.    for i in range(1,7):
020.        conta_i = resultado.count(i)
021.        conta = conta + (conta_i,)
022.    return conta
023. 
024. 
025.def histograma(res):
026.    posx = -100
027.    posy = 0
028.    comp_linha = 80
029.    for i,alt in enumerate(res):
030.        # desenha caso i
031.        desenha(i+1,alt,posx,posy,comp_linha)
032.        # define parâmetros
033.        posx = posx + comp_linha
034. 
035.def desenha(i,alt, posx,posy,comp_linha):
036.    # escreve número i
037.    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')   
038.    # desenha linha
039.    linha(posx,posy,comp_linha,'red')
040.    # desenha coluna
041.    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
042.    # escreve número de vezes (alt) que saiu o número i 
043.    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')
044. 
045. 
046.def coluna(posx,posy, lado_1, lado_2,cor):
047.    # posiciona
048.    turtle.penup()
049.    turtle.goto(posx,posy)
050.    turtle.pendown()
051.    # desenha
052.    turtle.color(cor)
053.    turtle.begin_fill()
054.    for i in range(2):
055.        turtle.forward(lado_1)
056.        turtle.left(90)
057.        turtle.forward(lado_2)
058.        turtle.left(90)
059.    turtle.end_fill()
060.    turtle.hideturtle()
061.     
062.def linha(posx,posy,comp_linha,cor):
063.    # posiciona
064.    turtle.penup()
065.    turtle.goto(posx,posy)
066.    turtle.color(cor)
067.    turtle.pendown()
068.    # linha
069.    turtle.forward(comp_linha)
070.    turtle.hideturtle()
071.     
072.def escreve_numero(posx,posy,fonte,valor,cor):
073.    # posiciona
074.    turtle.penup()
075.    turtle.goto(posx,posy)
076.    turtle.color(cor) 
077.    turtle.pendown()
078.    # escreve
079.    turtle.write(valor,font=fonte)
080.    turtle.hideturtle()
081.   
082.     
083.     
084.     
085.if __name__ == '__main__':
086.    n = 100
087.    teste = (46,39,105,0,44,5)
088.    fonte = ('Arial', 24, 'bold')
089.    posx = 0
090.    posy = 0
091.    cor_1 = 'black'
092.    cor_2 = 'red'
093.    comp = 80
094.    lado_1 = comp/2
095.    lado_2 = 50
096.    #escreve_numero(posx,posy,fonte,n,cor)
097.    #linha(posx,posy,comp,cor_2)
098.    #coluna(posx,posy, lado_1, lado_2,cor_2)
099.    #desenha(4,100, posx,posy,comp)
100.    #histograma(teste)
101.    #print(lanca_dado(n))
102.    dados_histo(n)
103.    turtle.exitonclick()
104.     
105.    
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.
001.import turtle
002.import random
003. 
004.def dados_histo(n):
005.    # experiência de lançamentos
006.    res = lanca_dado(n)
007.    # visualizaçao
008.    histograma(res)
009.     
010.     
011.def lanca_dado(n):
012.    # lançamento
013.    resultado = tuple()
014.    for i in range(n):
015.        numero = random.randint(1,6)
016.        resultado = resultado + (numero,)
017.    # contagem
018.    conta = tuple()
019.    for i in range(1,7):
020.        conta_i = resultado.count(i)
021.        conta = conta + (conta_i,)
022.    return conta
023. 
024. 
025.def histograma(res):
026.    posx = -100
027.    posy = 0
028.    comp_linha = 80
029.    for i,alt in enumerate(res):
030.        # desenha caso i
031.        desenha(i+1,alt,posx,posy,comp_linha)
032.        # define parâmetros
033.        posx = posx + comp_linha/2
034. 
035.def desenha(i,alt, posx,posy,comp_linha):
036.    # escreve número i
037.    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')   
038.    # desenha coluna
039.    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
040.    # escreve número de vezes (alt) que saiu o número i 
041.    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')
042. 
043. 
044.def coluna(posx,posy, lado_1, lado_2,cor):
045.    # posiciona
046.    turtle.penup()
047.    turtle.goto(posx,posy)
048.    turtle.pendown()
049.    # desenha
050.    turtle.pencolor('black')
051.    turtle.fillcolor(cor)
052.    turtle.begin_fill()
053.    for i in range(2):
054.        turtle.forward(lado_1)
055.        turtle.left(90)
056.        turtle.forward(lado_2)
057.        turtle.left(90)
058.    turtle.end_fill()
059.    turtle.hideturtle()
060.     
061.def linha(posx,posy,comp_linha,cor):
062.    # posiciona
063.    turtle.penup()
064.    turtle.goto(posx,posy)
065.    turtle.color(cor)
066.    turtle.pendown()
067.    # linha
068.    turtle.forward(comp_linha)
069.    turtle.hideturtle()
070.     
071.def escreve_numero(posx,posy,fonte,valor,cor):
072.    # posiciona
073.    turtle.penup()
074.    turtle.goto(posx,posy)
075.    turtle.color(cor) 
076.    turtle.pendown()
077.    # escreve
078.    turtle.write(valor,font=fonte)
079.    turtle.hideturtle()
080.   
081.     
082.     
083.     
084.if __name__ == '__main__':
085.    n = 1000
086.    teste = (46,39,105,0,44,5)
087.    fonte = ('Arial', 24, 'bold')
088.    posx = 0
089.    posy = 0
090.    cor_1 = 'black'
091.    cor_2 = 'red'
092.    comp = 80
093.    lado_1 = comp/2
094.    lado_2 = 50
095.    #escreve_numero(posx,posy,fonte,n,cor)
096.    #linha(posx,posy,comp,cor_2)
097.    #coluna(posx,posy, lado_1, lado_2,cor_2)
098.    #desenha(4,100, posx,posy,comp)
099.    #histograma(teste)
100.    #print(lanca_dado(n))
101.    dados_histo(n)
102.    turtle.exitonclick()
103.    
Para o leitor: e se quisermos que o histograma possa ter uma orientação qualquer??

Sem comentários:

Enviar um comentário