domingo, 22 de dezembro de 2013

Barras paralelas

Numa das últimas aulas estivemos a preparar o último teste. Um dos exemplos procurava passar por alguns dos aspectos essenciais: dicionários e ficheiros. O problema consistia em escrever um programa que realizasse quatro tarefas:

1- simular o lançamento de dois dados guardando o resultado da sua soma numa lista,

2- guardar os resultados num ficheiro, com um resultado por linha;

3- construir um dicionário de frequências em que as chaves fossem os números e os valores o número de vezes que ocorreram;

5- apresentar um gráfico de barras com o histograma das frequências. O gráfico seria feito com a ajuda do módulo turtle.

Os três primeiros sub-problemas eram revisitações de algo que já tínhamos feito. A nossa ideia era consolidar os conceitos e aproveitar para mais um exemplo de decomposição de problemas em sub-problemas e das quesdtões que se levantam.

Cada um deles podia ser resolvido de modo simples. Comecemos pelo problema da geração.
01.import random
02. 
03.def gera_1(n):
04.    lista = list()
05.    for i in range(n):
06.        lista.append(random.randint(1,6)+random.randint(1,6))
07.    return lista
08. 
09.def gera_2(n):
10.    return [random.randint(1,6) + random.randint(1,6) for i in range(n)]
11. 
12.def dado():
13.    return random.randint(1,6)
14. 
15.def gera_3(n):
16.    return [dado()+dado() for i in range(n)]
A primeira solução é a mais básica. A outras duas usam listas por compreensão, sendo que a última abstrai o problema de lançar o dado. Guardar os dados gerados num ficheiro também é trivial.
1.def guarda(ficheiro,dados):
2.    with open(ficheiro,'w') as f_out:
3.        for dado in dados:
4.            f_out.write(str(dado)+'\n')
Aqui usámos o gestor de contexto with. Finalmente ir buscar os dados ao ficheiro e construir um dicionário de frequências.
01.def freq(ficheiro):
02.    with open(ficheiro) as f_in:
03.        dados_str = f_in.read().split()
04.        dados_num = [eval(dado) for dado in dados_str]
05.        # dicio frequências
06.        dicio = dict()
07.        for dado in dados_num:
08.            dicio[dado] = dicio.get(dado,0) + 1
09.    return dicio
Estas três soluções têm que ser integradas num único programa. Só precisamos ter em atenção a interface.
1.def main(n, ficheiro):
2.    # gerar
3.    dados = gera_2(n)
4.    # guardar
5.    guarda(ficheiro, dados)
6.    # frequência
7.    frequencia = freq(ficheiro)
Agora fica a questão das barras e do uso do turtle. Admitamos que queremos fazer um gráfico como o da figura:
Vamos tentar decompor tudo em sub-problemas mais simples: os eixos, as barras e os valores. Vamos começar pelas barras e fazer algo como no caso do lançamento dos dados: começar pela questão de visualizar uma barra.
01.import turtle
02. 
03.def barra(pos,altura,cor, espessura):
04.    # posiciona
05.    turtle.penup()
06.    turtle.goto(pos)
07.    turtle.pendown()
08.    turtle.setheading(90)
09.    # atributos
10.    turtle.color(cor)
11.    turtle.width(espessura)
12.    # Desenha
13.    turtle.forward(altura)
A ideia desta solução é trivial: posicionar a tartaruga, colocá-la a apontar para norte e avançar um valor igual à altura da barra.

Desenhar um gráfico de barras pode ser agora feito chamando repetidas vezes a função barra.
1.def grafico_barras(pos_inicial,lista_barras, cor, espessura,delta):
2.    pos = pos_inicial
3.    for altura in lista_barras:
4.        barra(pos,altura,cor, espessura)
5.        # posiciona)
6.        pos = (turtle.xcor()+ delta,0)
7.    turtle.hideturtle()
O parâmetro delta controla o afastamento entre as barras. Passemos aos eixos. Pode ser feito de diferentes maneiras. Um exemplo:
01.def eixos(pos_x, pos_y, tam_x, tam_y):
02.    # posiciona
03.    turtle.penup()
04.    turtle.goto(pos_x, pos_y)
05.    turtle.pendown()
06.    # eixo x
07.    turtle.forward(tam_x)
08.    turtle.write('X', font=('Arial',8,'bold'))
09.    # posiciona
10.    turtle.penup()
11.    turtle.goto(pos_x, pos_y)
12.    turtle.pendown()
13.    # eixo y
14.    turtle.setheading(90)
15.    turtle.forward(tam_y)
16.   
17.    turtle.write('Y', font=('Arial',8,'bold'))
18.    turtle.hideturtle()
Usamos a função write para escrever o nome dos eixos. Notar como se controla a escrita. É evidente que podemos ter o desenho dos eixos e das barras tudo junto:
1.def grafico_barras_eixos(pos_inicial,lista_barras, cor, espessura,delta):
2.    eixos(pos_inicial[0] -delta, pos_inicial[1], len(lista_barras) * (espessura + delta), max(lista_barras) + delta)
3.    pos = pos_inicial
4.    for altura in lista_barras:
5.        barra(pos,altura,cor, espessura)
6.        # posiciona)
7.        pos = (turtle.xcor()+ delta,0)
8.    turtle.hideturtle()
Notar como se controla a dimensão dos eixos! Falta acrescentar os números. Mas isso pode ser feito com uma pequena alteração no programa que desenha as barras.
01.def grafico_barras_eixos(pos_inicial,lista_barras, cor, espessura,delta):
02.    eixos(pos_inicial[0] -delta, pos_inicial[1], len(lista_barras) * (espessura + delta), max(lista_barras) + delta)
03.    pos = pos_inicial
04.    for altura in lista_barras:
05.        barra(pos,altura,cor, espessura)
06.        turtle.pencolor('black')
07.        turtle.write(altura, font=('Arial',8,'bold'))
08.        # posiciona)
09.        pos = (turtle.xcor()+ delta,0)
10.    turtle.hideturtle()
Podemos juntar esta solução ao resto do programa, definindo um novo main.
01.def main(n, ficheiro):
02.    # gerar
03.    dados = gera_2(n)
04.    print(dados)
05.    # guardar
06.    guarda(ficheiro, dados)
07.    # frequência
08.    frequencia = freq(ficheiro)
09.    print(frequencia)
10.    # gráfico
11.    valores = [frequencia.get(chave,0) for chave in range(1,13)]
12.    grafico_barras_eixos((0,0),valores,'red',5,15)
Se executarmos o programa, no entanto, o resultado não é muito famoso. O problema está no facto de os valores serem muito pequenos. Uma solução simples é definir um factor de escala que dependerá do número de lançamentos de dados. Interessa-nos uma alteração que aumente muito os valores pequenos de n e pouco os valores grandes. Uma função candidata a tal é a função logaritmo!
01.def main(n, ficheiro):
02.    # gerar
03.    dados = gera_2(n)
04.    print(dados)
05.    # guardar
06.    guarda(ficheiro, dados)
07.    # frequência
08.    frequencia = freq(ficheiro)
09.    print(frequencia)
10.    # gráfico
11.    escala = int(math.log(n,10)) # <---
12.    valores = [escala * frequencia.get(chave,0) for chave in range(1,13)]
13.    grafico_barras_eixos((0,0),valores,'red',5,15)
14.    turtle.exitonclick()
Mas podemos promover outra pequena alteração para poder colocar a identificação dos números ao longo dos eixos dos XX.
A mudança principal envolve a função barra.
01.def barra(pos,altura,cor, espessura,num):
02.    if altura:
03.        # posiciona
04.        turtle.penup()
05.        turtle.goto(pos[0],pos[1]-20)
06.        turtle.write(num)
07.        turtle.goto(pos)
08.        turtle.pendown()
09.        turtle.setheading(90)
10.        # atributos
11.        turtle.color(cor)
12.        turtle.width(espessura)      
13.        # Desenha
14.        turtle.forward(altura)
15.        turtle.pencolor('black')
16.        turtle.write(altura, font=('Arial',8,'bold')) 
Notar que passámos a fazer aqui toda a parte que interssa saber da barra: valor de x, de y e o desenho.

Para quem gosta de tudo muito direitinho aqui fica o programa final completo.
01.import random
02.import operator
03.import math
04. 
05.def gera_2(n):
06.    return [random.randint(1,6) + random.randint(1,6) for i in range(n)]
07. 
08.# Guardar
09.def guarda(ficheiro,dados):
10.    with open(ficheiro,'w') as f_out:
11.        for dado in dados:
12.            f_out.write(str(dado)+'\n')
13.     
14.# Frequência
15.def freq(ficheiro):
16.    with open(ficheiro) as f_in:
17.        dados_str = f_in.read().split()
18.        dados_num = [eval(dado) for dado in dados_str]
19.        # dicio frequências
20.        dicio = dict()
21.        for dado in dados_num:
22.            dicio[dado] = dicio.get(dado,0) + 1
23.    return dicio
24. 
25.# Ver
26.import turtle
27. 
28.def barra(pos,altura,cor, espessura,num):
29.    if altura:
30.        # posiciona
31.        turtle.penup()
32.        turtle.goto(pos[0],pos[1]-20)
33.        turtle.write(num)
34.        turtle.goto(pos)
35.        turtle.pendown()
36.        turtle.setheading(90)
37.        # atributos
38.        turtle.color(cor)
39.        turtle.width(espessura)      
40.        # Desenha
41.        turtle.forward(altura)
42.        turtle.pencolor('black')
43.        turtle.write(altura, font=('Arial',8,'bold'))       
44. 
45.def grafico_barras_eixos(pos_inicial,lista_barras, cor, espessura,delta):
46.    eixos(pos_inicial[0] - delta, pos_inicial[1], len(lista_barras) * (espessura + delta), max(lista_barras) + delta)
47.    pos = pos_inicial
48.    for num,altura in enumerate(lista_barras):
49.        barra(pos,altura,cor, espessura,num)
50.        # posiciona
51.        pos = (turtle.xcor()+ delta,pos_inicial[1])
52.    turtle.hideturtle()
53. 
54.def eixos(pos_x, pos_y, tam_x, tam_y):
55.    # posiciona
56.    turtle.penup()
57.    turtle.goto(pos_x, pos_y)
58.    turtle.pendown()
59.    # eixo x
60.    turtle.forward(tam_x)
61.    turtle.write('X', font=('Arial',8,'bold'))
62.    # posiciona
63.    turtle.penup()
64.    turtle.goto(pos_x, pos_y)
65.    turtle.pendown()
66.    # eixo y
67.    turtle.setheading(90)
68.    turtle.forward(tam_y)
69.    turtle.write('Y', font=('Arial',8,'bold'))
70.    turtle.hideturtle()
71.     
72.# MAIN ----------------
73.def main(n, ficheiro):
74.    # gerar
75.    dados = gera_2(n)
76.    print(dados)
77.    # guardar
78.    guarda(ficheiro, dados)
79.    # frequência
80.    frequencia = freq(ficheiro)
81.    # gráfico
82.    escala = int(math.log(n,10)) # <--
83.    valores = [escala * frequencia.get(chave,0) for chave in range(1,13)]
84.    grafico_barras_eixos((0,0),valores,'red',5,15)
85.    turtle.exitonclick()
86.     
87.     
88.     
89. 
90.if __name__ == '__main__':
91.    main(1000,'/Users/ernestojfcosta/tmp/tudo.txt')
Para quem gosta de experiências, tente executar o programa para diferentes valores crescentes de n. Os gráficos de barras que são desenhados vão convergir para uma forma de distribuição conhecida em probabilidades: a distribuição normal. é uma boa altura para revisitar a Lei dos Grandes Números.

Boas Festas!

Sem comentários:

Enviar um comentário