domingo, 22 de dezembro de 2013

Bonecada

Acabámos de ver como podemos usar o módulo turtle para resolver um problema de visualização de um gráfico de barras. Notámos várias dificuldades que procurámos resolver de modo mais ou menos satisfatório. Do exercício resulta a ideia de gráficos em Python são um tormento. Nada de mais errado. O que precisamos é usar a ferramenta certa. É o que vamos fazer agora mostrando como podemos resolver o mesmo problema usando o módulo matplotlib.

Comecemos pelo exemplo básico.
import random
import matplotlib.pyplot as plt


def gera(n):
    return [random.randint(1,6) + random.randint(1,6) for i in range(n)]

def histograma(dados):
    plt.hist(dados,11)
    plt.show()    
    
if __name__ == '__main__':
    dados = gera(5000)
    histograma(dados)
Correndo o programa aparece-nos a bela imagem seguinte.
Fantástico, não é. Mas ainda podemos introduzir um pouco de cosmética...
def histograma(dados):
    plt.title('Dados = ' + str(len(dados)))
    plt.xlabel('Resultado')
    plt.ylabel('Frequência')
    plt.hist(dados,11,color='red')
    plt.show()  
... para obter um resultado diferente!
E a Lei dos Grandes Números? ainda se lembra?
Usar o matplotlib é mais simples ... e bem mais rápido! Claro que também o podemos usar para outro tipo de gráficos. Por exempo, o gráfico de uma função y=f(x).
def grafico(x,y):
    plt.title('y=f(x)')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.plot(x,y,'go-')
    plt.show()
E obtemos com esta configuração o resultado:
As possibilidades do matplotlib são imensas. O manual recente do módulo pode ser obtido em Manual

Divirta-se.

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.
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!

sábado, 21 de dezembro de 2013

Teste #3 - TP9

Pergunta 1

Explique o que vai aparecer no lugar dos pontos de interrogação (linhas 7 e 11).
>>> def toto(n):
... res = n.append(4)
... return res
...
>>> n = (5,6,7)
>>> print(toto(n))
Traceback (most recent call last): # Explicação: Erro porque append não é método de tuplos!
...
builtins.AttributeError: 'tuple' object has no attribute 'append'
>>> n
(5, 6, 7) # Explicação: n é imutável não sendo afectado pela chamada de toto(n).
Pergunta 2

Admita que tem guardado no dicionário informação relativa ao desempenho das equipas envolvidas num campeonato desportivo. A cada jornada pares de equipas jogam entre si. O dicionário está organizado de modo que as chaves são os nomes dos clubes, e os valores uma lista com o número de vitórias, de empates e de derrotas, por esta ordem, obtidos nos diferentes jogos em que a equipa esteve envolvida. Escreva um programa que dado o dicionário com a informação referida me devolva uma lista das equipas que obtiveram o maior número de vitórias.

Solução

Existem várias alternativas. Aqui optou-se por ir buscar toda a informação ao dicionário e passá-la para uma lista. De seguida os pares (clube, vitórias) é inspeccionado na procura do(s) de maior vitórias. Dentro do ciclo for fazemos o teste. Se encontramos algum clube com um novo máximo recomeçamos o processo com uma lista com esse clube apenas. Cada vez que encontramos um clube com o um valor igual ao máximo, acrescentamos o clube à lista dos clubes com número máximo de vitórias.
def mais_vict(campeonato):
    res = list(campeonato.items())
    max_v = 0
    clubes = []
    for c,r in res:
        if r[0] > max_v:
            max_v = r[0]
            clubes = [c]
        elif r[0] == max_v:
            clubes.append(c)
    return (clubes, max_v)
Pergunta 3 Suponha que tem guardado em disco um ficheiro de texto. Pretende analisar o conteúdo do texto para determinar a lista das palavras que nele ocorrem com uma frequência superior a um dado valor. Escreva um programa que dado um ficheiro e o valor de referência determina a lista das palavras que ocorrem um número de vezes superior a esse valor.

Solução

O que vamos fazer é começar por construir um dicionário de frequências para as palavras do ficheiro. Depois percorremos o dicionário identificando as palavras que ocorrem um número de vezes superior ao valor de referência (limiar).
def lista_freq(ficheiro,limiar):
    f_ent = open(ficheiro, 'r', enconding='utf8')
    palavras = f_ent.read().replace('\n',' ').split()
    dicio = {}
    for pal in palavras:
        dicio[pal] = dicio.get(pal,0) + 1
    lista = []
    for pal,num_ocor in dicio.items():
        if num_ocor > limiar:
            lista.append(pal)
    return lista

Teste #3 - TP 3

Pergunta 1
Explicar o que aparece nos pontos de interrogação (linhas 6 e 8).
>>> def titi(n):
... n.append([4])
...
>>> x = [1,2,3]
>>> print(titi(x))
None # Explicação: Porque a definição titi não tem return! Todavia n é alterado
>>> x
[1, 2, 3, [4]] #Explicação:  x é um objecto mutável pelo que a operação append sobre n também afecta x
Pergunta 2
Admita que tem guardado no dicionário informação relativa ao desempenho das equipas envolvidas num campeonato desportivo. A cada jornada pares de equipas jogam entre si. O dicionário está organizado de modo que as chaves são os nomes dos clubes, e os valores uma lista com o número de vitórias, de empates e de derrotas, por esta ordem, obtidos nos diferentes jogos em que a equipa esteve envolvida. Escreva um programa que dado o dicionário com a informação referida me devolva uma lista das equipas e sua pontuação, ordenada de modo que as mais pontuadas apareçam primeiro. Admita que uma vitória vale 3 pontos, um empate 1 ponto e uma derrota 0 pontos.

Solução

Vamos resolver este problema por decomposição em três sub-problemas. Num criamos uma estrutura, uma lista, onde colocamos pares (pontuação, nome_clube). Depois ordenamos a lista de modo decrescente pela pontuação. Finalmente, reconstruímos a lista no formato desejado. Esta última tarefa resulta do facto de termos construído os pares com a ordem “trocada” para nos facilitar o ordenamento.
def classif(campeonato):
    final = []
    for clube, result in campeonato.items():
        pontos = 3 * result[0] + result[1]
        final.append([pontos,clube])
    final.sort(reverse=True)
    lista = [[elem[1], elem[0]] for elem in final]
    return lista
Claro que existem outras formas de resolver esta questão. Por exemplo, para os que aprenderam nas aulas que existe um módulo em Python, chamado operator, que disponibiliza um método, itemgetter, para ordenar uma lista cujos elementos são eles próprios listas ou tuplos, uma alternativa era:
import operator   

def classif(campeonato):
    final = []
    for clube, result in campeonato.items():
        pontos = 3 * result[0] + result[1]
        final.append((clube, pontos))
    final.sort(key=operator.itemgetter(1), reverse=True)
    return final
Pergunta 3

Suponha que tem guardado em disco um ficheiro de texto. Pretende analisar o conteúdo do texto para determinar se ele é suspeito ou não. Um texto é suspeito se nele constarem pelo menos metade das palavras de uma lista de palavras proibidas. Escreva um programa que dado um ficheiro e uma lista de palavras proibidas determina se ele é ou não suspeito.

Solução

A solução mais simples consiste em extrair a informação do ficheiro na forma de uma lista das suas palavras, fazer a contagem do número de palavras proibidas que ocorrem no texto, e depois fazer a comparação.
def suspeito(ficheiro,palavras):
    f_ent = open(ficheiro, 'r', enconding='utf8')
    pal_ficheiro = f_ent.read().replace('\n',' ').split()
    f_ent.close()
    conta = 0
    for pal in palavras:
        if pal in pal_ficheiro:
            conta += 1
    if conta >= len(palavras)//2:
        return True
    else:
        return False
Optámos for fazer a contagem num ciclo controlado pelas palavras proibidas. Podíamos fazer semelhante mas agora com o ciclo a ser controlado pelas palavras do texto. Será interessante analisar qual das duas soluções é melhor. Isso depende to tamanho relativo das duas listas e do custo de procurar uma palavra num texto (instrução if pal in palavras).