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

sábado, 23 de novembro de 2013

Pirâmides e mais pirâmides: reflexões sobre um teste

O segundo teste de IPRP era fácil. Deliberadamente fácil! As questões colocadas foram várias vezes discutidas nas aulas e, por isso, esperava que o vosso desempenho fosse melhor do que o do primeiro teste. Assim aconteceu! No entanto, ainda existe muita dificuldade em entender os enunciados e, mesmo depois de entendidos, em passar do problema à solução. Nas turmas TP3 e TP9, a primeira pergunta era básica, a segunda envolvia o padrão ciclo-acumulador e a terceira revisitava o nosso velho conhecido módulo turtle, associado a um problema muito discutido nas aulas em volta da ideia de programação por analogia. Neste post vou retomar este último aspecto. O código aqui apresentado já foi dado na sua generalidade, mas vamos também apresentar versões mais simples e uma questão nova.

Comecemos pela pirâmide normal.
Uma solução possível consiste em ir imprimindo linha a linha, uma sequência de quadrados de um dado comprimento. Precisamos de saber duas coisas: (1) onde colocar a tartaruga no início de cada linha e, (2) onde recolocar a tartaruga para desenhar um quadrado dentro de cada linha. Definimos dois pontos de referência (posx e posy) e é em relação a eles que resolvemos a primeira questão. A segunda questão resolve-se depois de verificar que cada quadrado numa dada linha deve avançar na coordenada xx de um valor igual ao lado. O nosso programa vai assim funcionar de acordo com o que mostramos no desenho seguinte.
Posto isto o código completo.
import turtle
    
def quadrado(posx, posy,lado):
    turtle.showturtle()
    # posiciona
    turtle.penup()
    turtle.goto(posx, posy)
    turtle.pendown()
    # desenha
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.hideturtle()
    
# Normal
def pir_normal(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha linha i
        # --- posiciona
        turtle.penup()
        turtle.goto(posx + (n-i)*lado/2, posy + (n-i)*lado)
        turtle.pendown()      
        # --- desenha 
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
    turtle.hideturtle()
Quando nos pedem para desenhar a pirâmide, mas agora invertida.
Não temos mais do que usar a mesma lógica de há pouco e que a figura seguinte explicitas.
Uma vez mais partimos de uma posição de referência para gerar as posições seguintes pela ordem da figura. Falta o código (agora só com a parte de desenho da pirâmide).
# Invertida
def pir_invertida(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha linha i
        # --- posiciona
        turtle.penup()
        turtle.goto(posx+ (n-i)*lado/2,posy-(n-i)*lado)
        turtle.pendown()      
        # --- desenha 
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)    
    turtle.hideturtle()  
Paremos um pouco para ver a diferença entre os dois programas. Um olhar atento verificará que só numa instrução, a que calcula os pontos 1,2,3 e 4, há uma pequena diferença, que faz toda a diferença: num caso o valor de vyy vai diminuindo e no outro vai aumentando. Apenas isto!!

Passemos agora ao exemplo do teste em que se pretende uma forma “voltada” para a direita.
A grande diferença em relação aos dois casos anteriores, é que uma solução fácil envolve olhar para a figura como colunas de quadrados de um dado tamanho. Daí que agora o que se vai mudar dentro do segundo ciclo for é o valor da coordenada yy. O resto, cálculo das posições em cada coluna é semelhante.
Vamos ao código.
def pir_direita(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha coluna i
        # -- posiciona
        turtle.penup()
        turtle.goto(posx+(n-i)*lado,posy + (n-i)*lado/2)
        turtle.pendown()      
        # -- desenha
        for j in range(1,i+1):            
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.sety(turtle.ycor()+lado)
    turtle.hideturtle() 
Mudemos agora a figura de modo a estar “voltada” para a esquerda.
O que precisamos, também aqui, é saber calcular os pontos de referência para cada coluna. A figura seguinte mostra quais são.
Ilustremos agora com o código.
# Esquerda
def pir_esquerda(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha coluna i
        # posiciona
        turtle.penup()
        turtle.goto(posx - (n-i)*lado,posy+(n-i)*lado/2)
        turtle.pendown()      
        # desenha
        for j in range(1,i+1):        
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.sety(turtle.ycor()+lado)
    turtle.hideturtle()
Mais uma vez qual a diferença entre os dois últimos programas? Apenas um sinal dentro de uma instrução, só que agora é no caso da coordenada xx.

Estes exemplos mostram como por vezes uma simples alteração resolve um problema diferente. As soluções propostas também são consequência do modo como olhamos para as figuras e entendemos tratar-se de uma sequência de linhas ou uma sequência de colunas. Mas não podíamos ter olhado para as figuras como sequências de “diagonais”? claro que sim. Isso interfere com a construção da solução? Um pouco. Admitamos que é o exemplo da figura voltada para a direita. Eis o resultado.
# Diagonal
def pir_diagonal(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha linha i
        # posiciona
        turtle.penup()
        turtle.goto(posx,posy-i*lado)
        turtle.pendown()      
        # desenha
        for j in range(i,n+1):           
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.penup()
            turtle.goto(turtle.xcor() + lado,turtle.ycor()-lado/2)
            turtle.pendown()
    turtle.hideturtle()
Tente identificar nesta solução quais os pontos que estão a ser calculados. Para ajudar à visão vamos incorporar cor na nossa pirâmide.
# Com cores
def pir_diagonal_cores(n,posx, posy,lado,cores):
    for i in range(1,n+1):
        # desenha linha i
        # posiciona
        turtle.penup()
        turtle.goto(posx,posy-i*lado)
        turtle.pendown()      
        # desenha
        turtle.fillcolor(cores[i%len(cores)])
        for j in range(i,n+1):
            turtle.begin_fill()
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.end_fill()
            turtle.penup()
            turtle.goto(turtle.xcor() + lado,turtle.ycor()-lado/2)
            turtle.pendown()
    turtle.hideturtle()
Executando o código obtemos a figura seguinte, em que as cores tornam visível a lógica utilizada na solução.
Mas será que ainda podemos mudar de perspectiva, nem linhas, nem colunas, nem diagonais? Neste caso ainda é. Uma das soluções apresentadas no teste faz isso mesmo: não há linhas, nem, colunas, nem diagonais, mas apenas quadrados.
# minimalista
def pir_minimalista(n, lado):
    for i in range(n):
        for j in range(n-i):
            quadrado(i*lado,j*lado + i*(lado/2),lado)
Trata-se de uma solução minimalista, sem ponto inicial de referência (não tem parâmetros posx e posy) e em que se calcular tantos pontos quantos os quadrados na pirâmide... Tente perceber como tal é feito.

E agora vamos descansar...

Teste #1 - Alunos 3ª Fase

Pergunta 1

Quais as três características comuns a todos os objectos?

Todos os objectos têm identidade (o seu endereço na memória), valor (aquilo que o objecto é e sobre o quase incidem as operações) e tipo (que identifica o conjunto de valores e de operações que podem ser feitas sobre um objecto do tipo).

Pergunta 2

Desenvolver um programa modular para desenhar a figura: A técnica é sempre a mesma: olhar e ver. Neste caso vemos três triângulos em posições diversas. Vamos então construir um programa para desenhar um triângulo, em função do lado e da posição.
def triangulo(posx, posy,lado):
    turtle.showturtle()
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    for i in range(3):
        turtle.forward(lado)
        turtle.left(120)
    turtle.hideturtle()
O resto é trivial: desenhamos os triângulos em sequência. Só precisamos estar atentos à posição e à orientação.
def tri_tri(posx,posy,lado):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # triangulo 1
    triangulo(turtle.xcor(),turtle.ycor(),lado)
    # triangulo 2
    turtle.setx(turtle.xcor() + lado)
    triangulo(turtle.xcor(),turtle.ycor(),lado)
    # triangulo 3
    turtle.left(120)
    turtle.forward(lado)
    turtle.right(120)
    triangulo(turtle.xcor(),turtle.ycor(),lado)
Claro que temo que admitir que alguém olhe para a figura e veja apenas dois triângulos: um exterior, maior, e outro interior, mais pequeno. A relação entre o comprimento dos respectivos lados é de um para dois. Com esta visão a solução é diferente:
def tri_tri_bis(posx, posy, lado):
    """ Faz desenho do triângulo exterior, seguido do desenho do triângulo interior."""
    # Posiciona
    turtle.showturtle()
    turtle.penup()
    turtle.goto(posx, posy)
    turtle.pendown()
    # Desenha triângulo exterior
    triangulo(turtle.xcor(),turtle.ycor(),lado)
    # Posiciona no meio da base do triângulo maior com a orientação certa
    turtle.penup()
    turtle.setx(turtle.xcor()+lado/2)
    turtle.pendown()   
    turtle.left(60)
    # Desenha triangulo interior
    triangulo(turtle.xcor(),turtle.ycor(), lado/2)
Pergunta 3

Pedem-nos um programa para manipular uma cadeia de caracteres trocando os seus n primeiros caracteres com os seus últimos n caracteres. Além disso temos que verificar se a operação é possível. A operação não poderá ser feita se o valor de n for maior do que metade do tamanho da sequência!
def troca_cad(cadeia,n):
    if len(cadeia) < 2*n:
        print('Impossível')
        return -1
    return cadeia[-n:] + cadeia[n:-n] + cadeia[:n] 
A solução faz o teste da possibilidade e, caso seja possível, constrói a nova cadeia de caracteres: os n últimos vêm para o início (cadeia[-n:]), os n primeiros vão para o fim (cadeia[:n]), e no meio fica o restante (cadeia[n:-n]).

quinta-feira, 21 de novembro de 2013

Teste # 2 - TP9

Pergunta 1

Listas são sequências (existe ordem), heterogéneas (os elementos da lista podem de qualquer tipo), mutáveis (é possível alterar o seu valor sem alterar a sua identidade.

Pergunta 2

def g(x,n):
    """calcula o valor aproximado de uma função g(x) pela soma de n parcelas."""
    soma = 0
    for i in range(n):
        soma += (-1)**i * (x**i) / factorial(i)
    return soma
Para quem não soubesse que o factorial existe no módulo math:
def factorial(x):
    fact = 1
    for i in range(1,x+1):
        fact *= i
    return fact
Pergunta 3

Só tem que comparar com o que foi feito nas aulas e apareceu aqui no blogue...
import turtle
  
def quadrado(posx, posy,lado):
    turtle.showturtle()
    # posiciona
    turtle.penup()
    turtle.goto(posx, posy)
    turtle.pendown()
    # desenha
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.hideturtle()
  
def pir_quadrados_ld(n,posx, posy,lado,cores):
    for i in range(n,0,-1):
        # desenha linha i
        # posiciona
        turtle.penup()
        turtle.sety(posy+ (n-i)*lado/2)
        turtle.pendown()      
        # desenha
        for j in range(1,i+1):
                                 
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.penup()
            turtle.sety(turtle.ycor()+lado)
            turtle.pendown()
        # muda de linha
        turtle.penup()
        turtle.goto(turtle.xcor()+lado,posy)
        turtle.pendown()
    turtle.hideturtle()