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.
import random
def gera_1(n):
lista = list()
for i in range(n):
lista.append(random.randint(1,6)+random.randint(1,6))
return lista
def gera_2(n):
return [random.randint(1,6) + random.randint(1,6) for i in range(n)]
def dado():
return random.randint(1,6)
def gera_3(n):
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.
def guarda(ficheiro,dados):
with open(ficheiro,'w') as f_out:
for dado in dados:
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.
def freq(ficheiro):
with open(ficheiro) as f_in:
dados_str = f_in.read().split()
dados_num = [eval(dado) for dado in dados_str]
# dicio frequências
dicio = dict()
for dado in dados_num:
dicio[dado] = dicio.get(dado,0) + 1
return dicio
Estas três soluções têm que ser integradas num único programa. Só precisamos ter em atenção a interface.
def main(n, ficheiro):
# gerar
dados = gera_2(n)
# guardar
guarda(ficheiro, dados)
# frequência
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.
import turtle
def barra(pos,altura,cor, espessura):
# posiciona
turtle.penup()
turtle.goto(pos)
turtle.pendown()
turtle.setheading(90)
# atributos
turtle.color(cor)
turtle.width(espessura)
# Desenha
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.
def grafico_barras(pos_inicial,lista_barras, cor, espessura,delta):
pos = pos_inicial
for altura in lista_barras:
barra(pos,altura,cor, espessura)
# posiciona)
pos = (turtle.xcor()+ delta,0)
turtle.hideturtle()
O parâmetro
delta controla o afastamento entre as barras.
Passemos aos eixos. Pode ser feito de diferentes maneiras. Um exemplo:
def eixos(pos_x, pos_y, tam_x, tam_y):
# posiciona
turtle.penup()
turtle.goto(pos_x, pos_y)
turtle.pendown()
# eixo x
turtle.forward(tam_x)
turtle.write('X', font=('Arial',8,'bold'))
# posiciona
turtle.penup()
turtle.goto(pos_x, pos_y)
turtle.pendown()
# eixo y
turtle.setheading(90)
turtle.forward(tam_y)
turtle.write('Y', font=('Arial',8,'bold'))
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:
def grafico_barras_eixos(pos_inicial,lista_barras, cor, espessura,delta):
eixos(pos_inicial[0] -delta, pos_inicial[1], len(lista_barras) * (espessura + delta), max(lista_barras) + delta)
pos = pos_inicial
for altura in lista_barras:
barra(pos,altura,cor, espessura)
# posiciona)
pos = (turtle.xcor()+ delta,0)
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.
def grafico_barras_eixos(pos_inicial,lista_barras, cor, espessura,delta):
eixos(pos_inicial[0] -delta, pos_inicial[1], len(lista_barras) * (espessura + delta), max(lista_barras) + delta)
pos = pos_inicial
for altura in lista_barras:
barra(pos,altura,cor, espessura)
turtle.pencolor('black')
turtle.write(altura, font=('Arial',8,'bold'))
# posiciona)
pos = (turtle.xcor()+ delta,0)
turtle.hideturtle()
Podemos juntar esta solução ao resto do programa, definindo um novo main.
def main(n, ficheiro):
# gerar
dados = gera_2(n)
print(dados)
# guardar
guarda(ficheiro, dados)
# frequência
frequencia = freq(ficheiro)
print(frequencia)
# gráfico
valores = [frequencia.get(chave,0) for chave in range(1,13)]
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!
def main(n, ficheiro):
# gerar
dados = gera_2(n)
print(dados)
# guardar
guarda(ficheiro, dados)
# frequência
frequencia = freq(ficheiro)
print(frequencia)
# gráfico
escala = int(math.log(n,10)) # <---
valores = [escala * frequencia.get(chave,0) for chave in range(1,13)]
grafico_barras_eixos((0,0),valores,'red',5,15)
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.
def barra(pos,altura,cor, espessura,num):
if altura:
# posiciona
turtle.penup()
turtle.goto(pos[0],pos[1]-20)
turtle.write(num)
turtle.goto(pos)
turtle.pendown()
turtle.setheading(90)
# atributos
turtle.color(cor)
turtle.width(espessura)
# Desenha
turtle.forward(altura)
turtle.pencolor('black')
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.
import random
import operator
import math
def gera_2(n):
return [random.randint(1,6) + random.randint(1,6) for i in range(n)]
# Guardar
def guarda(ficheiro,dados):
with open(ficheiro,'w') as f_out:
for dado in dados:
f_out.write(str(dado)+'\n')
# Frequência
def freq(ficheiro):
with open(ficheiro) as f_in:
dados_str = f_in.read().split()
dados_num = [eval(dado) for dado in dados_str]
# dicio frequências
dicio = dict()
for dado in dados_num:
dicio[dado] = dicio.get(dado,0) + 1
return dicio
# Ver
import turtle
def barra(pos,altura,cor, espessura,num):
if altura:
# posiciona
turtle.penup()
turtle.goto(pos[0],pos[1]-20)
turtle.write(num)
turtle.goto(pos)
turtle.pendown()
turtle.setheading(90)
# atributos
turtle.color(cor)
turtle.width(espessura)
# Desenha
turtle.forward(altura)
turtle.pencolor('black')
turtle.write(altura, font=('Arial',8,'bold'))
def grafico_barras_eixos(pos_inicial,lista_barras, cor, espessura,delta):
eixos(pos_inicial[0] - delta, pos_inicial[1], len(lista_barras) * (espessura + delta), max(lista_barras) + delta)
pos = pos_inicial
for num,altura in enumerate(lista_barras):
barra(pos,altura,cor, espessura,num)
# posiciona
pos = (turtle.xcor()+ delta,pos_inicial[1])
turtle.hideturtle()
def eixos(pos_x, pos_y, tam_x, tam_y):
# posiciona
turtle.penup()
turtle.goto(pos_x, pos_y)
turtle.pendown()
# eixo x
turtle.forward(tam_x)
turtle.write('X', font=('Arial',8,'bold'))
# posiciona
turtle.penup()
turtle.goto(pos_x, pos_y)
turtle.pendown()
# eixo y
turtle.setheading(90)
turtle.forward(tam_y)
turtle.write('Y', font=('Arial',8,'bold'))
turtle.hideturtle()
# MAIN ----------------
def main(n, ficheiro):
# gerar
dados = gera_2(n)
print(dados)
# guardar
guarda(ficheiro, dados)
# frequência
frequencia = freq(ficheiro)
# gráfico
escala = int(math.log(n,10)) # <--
valores = [escala * frequencia.get(chave,0) for chave in range(1,13)]
grafico_barras_eixos((0,0),valores,'red',5,15)
turtle.exitonclick()
if __name__ == '__main__':
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!