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.
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.
3.
dados
=
gera_2(n)
4.
5.
guarda(ficheiro, dados)
6.
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.
05.
turtle.penup()
06.
turtle.goto(pos)
07.
turtle.pendown()
08.
turtle.setheading(
90
)
09.
10.
turtle.color(cor)
11.
turtle.width(espessura)
12.
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.
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.
03.
turtle.penup()
04.
turtle.goto(pos_x, pos_y)
05.
turtle.pendown()
06.
07.
turtle.forward(tam_x)
08.
turtle.write(
'X'
, font
=
(
'Arial'
,
8
,
'bold'
))
09.
10.
turtle.penup()
11.
turtle.goto(pos_x, pos_y)
12.
turtle.pendown()
13.
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.
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.
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.
03.
dados
=
gera_2(n)
04.
print
(dados)
05.
06.
guarda(ficheiro, dados)
07.
08.
frequencia
=
freq(ficheiro)
09.
print
(frequencia)
10.
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.
03.
dados
=
gera_2(n)
04.
print
(dados)
05.
06.
guarda(ficheiro, dados)
07.
08.
frequencia
=
freq(ficheiro)
09.
print
(frequencia)
10.
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.
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.
11.
turtle.color(cor)
12.
turtle.width(espessura)
13.
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.
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.
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.
20.
dicio
=
dict()
21.
for
dado
in
dados_num:
22.
dicio[dado]
=
dicio.get(dado,
0
)
+
1
23.
return
dicio
24.
25.
26.
import
turtle
27.
28.
def
barra(pos,altura,cor, espessura,num):
29.
if
altura:
30.
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.
38.
turtle.color(cor)
39.
turtle.width(espessura)
40.
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.
51.
pos
=
(turtle.xcor()
+
delta,pos_inicial[
1
])
52.
turtle.hideturtle()
53.
54.
def
eixos(pos_x, pos_y, tam_x, tam_y):
55.
56.
turtle.penup()
57.
turtle.goto(pos_x, pos_y)
58.
turtle.pendown()
59.
60.
turtle.forward(tam_x)
61.
turtle.write(
'X'
, font
=
(
'Arial'
,
8
,
'bold'
))
62.
63.
turtle.penup()
64.
turtle.goto(pos_x, pos_y)
65.
turtle.pendown()
66.
67.
turtle.setheading(
90
)
68.
turtle.forward(tam_y)
69.
turtle.write(
'Y'
, font
=
(
'Arial'
,
8
,
'bold'
))
70.
turtle.hideturtle()
71.
72.
73.
def
main(n, ficheiro):
74.
75.
dados
=
gera_2(n)
76.
print
(dados)
77.
78.
guarda(ficheiro, dados)
79.
80.
frequencia
=
freq(ficheiro)
81.
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!