sábado, 21 de outubro de 2017

Exercícios de Programação Descendente (I)

Durante as aulas foi colocado o problema de desenvolver uma solução para a criação da imagem visual do símbolo da radioactividade.
Perante este problema a reacção primeira deve ser a de equacionar a possibilidade de decompor o problema em sub-problemas, se possível, independentes. Caso não sejam independentes temos que acordar primeiro o modo como se relacionam.

Não creio ser difícil identificar três sub-problemas: desenhar um quadrado, desenhar três sectores e desenhar uma circunferência. A dependência neste caso é a posição relativa de cada uma das três componentes. Com base nesta abordagem podemos escrever um primeiro esboço de solução.
import turtle

def radioactividade():
    # desenha quadrado
    quadrado()
    # desenha sectores
    sectores()
    #desenha circunferência
    circunferencia()
    
    
def quadrado():
    pass

def sectores():
    pass

def circunferencia():
    pass

if __name__ == '__main__':
    radioactividade()
Esta solução já pode ser testada embora não faça nada! Note-se que as definições ainda não têm argumentos… Vamos tomar nova decisão: dar o máximo liberdade à resolução de cada um dos sub-problemas. Por exemplo, no caso do quadrado, vamos criar uma definição que permita desenhar um quadrado parametrizado pelo tamanho do lado, a posição, a orientação e a cor. Já sabemos como fazer isso.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    sectores()
    #desenha circunferência
    circunferencia()
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores():
    pass

def circunferencia():
    pass

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 30
    cor = 'yellow'
    radioactividade(lado,posx,posy,orientacao,cor)
Podemos testar isoladamente a definição quadrado e/ou testá-la no contexto da definição do símbolo da radioactividade. O normal, em programas grandes é que o teste seja feito primeiro isoladamente e só depois no interior do programa principal. Trata-se de mais uma vantagem deste tipo de programação, apelidada de programação descendente, que conduz a soluções modulares. Para além de os testes serem mais fáceis de fazer, também ficamos com código reutilizável!

Resolvida esta questão vamos escolher um dos dois sub-problemas restantes. Optamos pela circunferência. Também aqui já sabemos o que fazer.
def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Se testarmos esta definição verificamos que está tudo funcional como pretendido. Naturalmente queremos testar esta solução no interior do nosso programa principal, e aqui, somos confrontados com o facto dos tamanhos (lado e raio), das posições e das orientações estarem relacionadas. Com um pouco de análise não será difícil chegar a uma solução aceitável.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor, cor_c):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    sectores()
    #desenha circunferência
    raio = lado/10
    posx_c = posx + lado/2 + raio
    posy_c = posy + lado/2
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores():
    pass

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 0
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    #circunf(raio,posx,posy,orientacao,cor)
    radioactividade(lado,posx,posy,orientacao,cor, cor_c)
    turtle.exitonclick()
Como se pode ver o desenho da circunferência é precedido do cálculo dos valores apropriados para o tamanho do raio, a posição e a orientação. Repare que para que o centro da circunferência coincida com o centro do quadrado, a tartaruga tem que ser colocada numa posição em que veja esse centro à sua esquerda e à distância do raio! O leitor atento notará que a orientação inicial é de zero graus, pois é isto que nos é pedido. Caso o valor seja diferente o posicionamento do centro da circunferência também é diferente.

Deixámos para o fim o problema dos sectores. Também aqui é possível decompor este problema em três sub-problemas equivalentes. Vejamos então como podemos desenhar um sector com total liberdade.
def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()
O desenho tem três partes: desenho do raio, desenho do arco, desenho do raio. Notar que no final queremos ter a tartaruga com a orientação inicial. Porquê?? Uma vez mais, um leitor atento pode ser levado a pensar que o desenho da circunferência e o desenho do sector poderiam ser unidos numa única definição. Afinal, parece que apenas diferem do parâmetro amplitude. Mas será mesmo assim? Quem quiser pode explorar esse caminho e verificar as questões que se colocam.

Como desenhamos os três sectores? Não é difícil perceber que temos que repetir o desenho de um sector alterando apenas a orientação de 120 graus. Será que o facto de a tartaruga que desenha um sector terminar com a mesma orientação que a inicial ajudou???
def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
Para terminar o trabalho temos que incorporar esta solução no programa principal.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    raio_s = lado/4
    posx_s = posx + lado/2
    posy_s = posy + lado/2
    orientacao_s = orientacao
    amplitude = 60
    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
    #desenha circunferência
    raio = lado/10
    posx_c = posx + lado/2 + raio
    posy_c = posy + lado/2
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
        

def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 0
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    raio_s = lado/4
    cor_s = 'black'
    amplitude = 60
    #circunferencia(raio,posx,posy,orientacao,cor)
    #sector(raio_s,posx,posy,orientacao,cor_s,amplitude)
    #sectores(raio_s,posx,posy,orientacao,cor_s,amplitude)
    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
    turtle.exitonclick()
A definição de alguns parâmetros dos sectores em função dos parâmetros do quadrado não deve oferecer dúvidas. Se executarmos o programa verificamos que não aparece a separação entre a circunferência e os sectores. A solução desse problema é trivial e passa por colocar a cor da caneta da tartaruga, quando desenha a circunferência, a branco.

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pencolor('white')
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Como dissemos atrás esta solução global funciona quando a orientação inicial é zero. Se for outra não funciona. Como alterar a nossa solução mexendo o mínimo possível, por forma ao programa funcionar mesmo quando o quadrado tem uma orientação qualquer? A solução passa por recalcular o centro da figura tendo a orientação em linha de conta. Para isso basta usar um pouco do nosso conhecimento de trigonometria.
import turtle
import math


def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    raio_s = lado/4
    ang_base = (orientacao* math.pi / 180)
    ang = ang_base + math.pi/4     
    posx_s = posx + lado/math.sqrt(2) * math.cos(ang) 
    posy_s = posy + lado/math.sqrt(2) * math.sin(ang)
    orientacao_s = orientacao
    amplitude = 60
    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
    #desenha circunferência
    raio = lado/10
    posx_c = posx_s +  raio * math.cos(ang_base)
    posy_c = posy_s + raio * math.sin(ang_base)
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
        

def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pencolor('white')
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 200
    posx = -50
    posy = -50
    orientacao = 45
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    raio_s = lado/4
    cor_s = 'black'
    amplitude = 60
    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
    turtle.exitonclick()
Como se pode ver apenas mexemos no programa principal e em zonas localizadas do código, as zonas de definem a interacção entre as partes. E pronto. Espero que da próxima vez que programar procure usar este princípio da decomposição de um problema em sub-problemas!

sexta-feira, 13 de outubro de 2017

Teste # 1 - TP2

P1

O que aparece no lugar do ponto de interrogação?
>>> x = 'abacadabra'
>>> x[1] = 'zeus'
Traceback (most recent call last):
  Python Shell, prompt 2, line 1
builtins.TypeError: 'str' object does not support item assignment
Aparece um erro pois as cadeias de caracteres são imutáveis não podendo o seu valor ser alterado.

P2

Como saber se uma moeda está enviesada? Fazemos vários lançamentos e comparamos com o valor esperado para uma das duas opções. Como nada neste mundo é perfeito aceitamos uma pequena discrepância em relação a esse valor. Para resolver o problema, vamos devagar e por partes. Primeiro uma versão simples que apenas simula e compara com o caçoe médio esperado.
def enviesada_a(n):
    # lança e conta
    conta = 0
    for i in range(n):
        conta = conta + random.randint(0,1)   
    # analisa
    return conta != n//2
Esta versão baseia-se num padrão dec programação conhecido por ciclo - acumulador. O nome conta está associado a um objecto cujo valor corresponde ao numero de vezes que já saiu caras (1). O ciclo é repetido o número de vezes pretendido. A comparação final é feita usando a divisão inteira.

Vamos partir desta solução para a solução final pretendida. A moeda estará enviesada se o valor obtido estiver fora de um dado intervalo.
def enviesada(n,perc):
    """ 
    n = número de lançamentos
    perc = percentagem aceitável [0,1]
    """
    # lança e conta
    conta = 0
    for i in range(n):
        conta = conta + random.randint(0,1)   
    # analisa
    espera = n/2
    inf_ = (1 - perc)* espera
    sup_ = (1 + perc) * espera
    return (conta < inf_) or (conta > sup_)
Como se observa usamos agora uma divisão de floats.

P3

Queremos desenhar bonecos como o da figura.
Olhando para a figura observamos que precisamos saber desenhar balões coloridos e uma cauda que é composta de repetições de uma sequência de quatro segmentos com orientações alternadas. Uma solução simples vai envolver três passos:
def boneco(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    # desenha balão grande
    # desenha balão pequeno
    # desenha cauda
    pass
Tratemos dos balões isoladamente:
def bola(raio, posx,posy, orientacao, cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.fillcolor(cor)
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Esta solução corresponde ao que já foi feito nas aulas!!! Vamos tratar da parte nova: a cauda. olhando para a figura vemos que é composta a partir de uma sequência de formas mais simples. Estas por sua vez são formadas por três traços. Eis uma solução genérica para a cauda:
def cauda(n, tipo, posx, posy,orientacao, comp, cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.pencolor(cor)
    for i in range(n):
        turtle.forward(comp)
        turtle.right(tipo * 60)     
    turtle.hideturtle()
Dizemos que é genérica porque usamos o parâmetro n, que no caso que nos interessa será igual a 3. Por outro lado, note-se que o parâmetro tipo é usado para determinar a orientação de cada sequência de três segmentos. tipo pode valer 1 ou -1, pois só temos duas orientações a considerar.

Resolvidas as três questões (balão grande, balão pequeno e cauda), vamos juntar tudo. A primeira questão é a de saber como juntamos os dois balões. A ideia é desenhar o maior e depois, a partir da posição final e da orientação, calcular a posição do centro do balão pequeno. Uma hipótese é:
def boneco(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    # desenha balão grande
    bola(raio_1,  posx,posy, orientacao, cor_1)
    # desenha balão pequeno
    turtle.penup()
    turtle.setheading(orientacao-90)
    turtle.forward(2*raio_2)
    turtle.setheading(orientacao)
    turtle.pendown()
    bola(raio_2,  turtle.xcor(),turtle.ycor(), orientacao, cor_2)
    # desenha cauda
   
Esta solução é fácil de entender se nos lembrarmos que a tartaruga desenha uma circunferência tendo o centro à sua esquerda! Claro que podemos fazer de outro modo:

def balao(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    bola(raio_1,  posx,posy, orientacao, cor_1)
    bola(-raio_2,  turtle.xcor(),turtle.ycor(), orientacao, cor_2)
Percebe a diferença???

Só falta acrescentar a cauda…
def balao(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    bola(raio_1,  posx,posy, orientacao, cor_1)
    turtle.penup()
    turtle.setheading(orientacao-90)
    turtle.forward(2*raio_2)
    turtle.setheading(orientacao)
    turtle.pendown()
    bola(raio_2,  turtle.xcor(),turtle.ycor(), orientacao, cor_2)
    for i in range(n):
        cauda(3,(-1)**i,turtle.xcor(),turtle.ycor(),turtle.heading(),20,cor_3)
    turtle.hideturtle()
E pronto! Percebeu o modo como alternamos a orientação da cauda??? Simplesmente fazendo o tipo igual a (-1)** i, o que faz com que o tipo vá ser alternadamente 1 e -1, como pretendido!

Pode usar este programa para criar variantes. Por exemplo:

def baloes(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    # balão grande
    bola(raio_1,  posx,posy, orientacao, cor_1)
    # n balões pequenos à volta do balão grande...
    for i in range(n):
        turtle.penup()
        turtle.circle(raio_1,360/n)
        turtle.pendown()        
        bola(-raio_2,  turtle.xcor(),turtle.ycor(), turtle.heading(), cor_2)
    turtle.hideturtle()

Teste # 1 - TP1

P1

Quando fazemos :
>>> X = X + 1 
acontece o seguinte. Primeiro o sistema tenta calcular o objecto associado à expressão X + 1. Para tal procura no espaço dos objectos o valor do objecto associado ao nome X. De seguida, incrementa esse valor de uma unidade e associa o novo objecto ao nome X.

P2

Era-nos pedido uma solução para o problema de saber se após o lançamento de um dado n vezes, o número de vezes que saiu um número par é maior do que o valor médio esperado. Podemos resolver este problema pro aproximações, baseando-nos num padrão de programação nosso conhecido: ciclo - acumulador.

def par_impar(n):
    conta_par = 0 # o acumulador
    for i in range(n):
        num = lanca_dado()
        # actualiza o acumulador
    # define resultado
A implementação da simulação do lançamento do dado é trivial:
import random 

def lanca_dado():
    return random.randint(1,6)
Com estes elementos chegamos facilmente à versão final:
import random

def lanca_dado():
    return random.randint(1,6)

def par_impar(n):
    conta_par = 0
    for i in range(n):
        num = lanca_dado()
        if (num == 2) or (num == 4) or (num == 6): # if num in (2,4,6):
            conta_par = conta_par + 1
    if conta_par > n/2:
        return True
    else:
        return False
Notar que o teste de saída de número par pode ser abreviado para if num in (2,4,6):

P3

Queremos um programa que nos permita desenhar figuras como a abaixo.

Pedem para poder parametrizar muita coisa: posição, orientação, número de laços e de segmentos tos, cor, tamanho do lado dos laços, tamanho dos segmentos, etc.

A solução passa por dividir o problema em sub-problemas e não tentar resolver tudo de uma vez. Olhando para a figura vemos laços e uma cauda. Os laços podem ser construídos como dois triângulos ligados por um vértice, enquanto a cauda é uma sequência de segmentos. Vamos resolver cada um dos sub-problemas.

Comecemos pelos triângulos coloridos, algo que fizemos nas aulas.
def tri_cor(posx,posy,orientacao,lado,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(3):
        turtle.forward(lado)
        turtle.left(120)
    turtle.end_fill()
    turtle.hideturtle()
Como se pode ver, iniciamos o programa definindo os parâmetros e depois desenhos o triângulo. O laço resulta de desenharmos dois triângulos percebendo que a orientação de ambos está desfasada de 180 graus.

def laco(posx,posy,orientacao,lado,cor):
    tri_cor(posx,posy,orientacao,lado,cor)
    tri_cor(posx,posy,orientacao + 180,lado,cor)
    turtle.hideturtle()
Passemos à cauda como sequência de segmentos. Desenhar um segmento com uma dada inclinação é trivial.

def seg(posx,posy,comp,orientacao,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.pencolor(cor)
    turtle.forward(comp)
    turtle.hideturtle()
Podemos agora juntar as peças do nosso puzzle. Se pensarmos um pouco, a estratégia mais interessante para o nosso programa final consiste em desenhar um segmento e de seguida desenhar um laço, repetindo estas acções o número apropriado de vezes. Daí a solução:

def boneco(n, posx, posy, orientacao,inc,comp,lado,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    for i in range(n):
        if i % 2 == 0:
            seg(turtle.xcor(),turtle.ycor(),comp,orientacao + inc,cor)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()-90,lado,cor)
        else:
            seg(turtle.xcor(),turtle.ycor(),comp,orientacao - inc,cor)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()+90,lado,cor)        
    if n % 2 == 0:
        seg(turtle.xcor(),turtle.ycor(),comp,orientacao + inc,cor)
    else:
        seg(turtle.xcor(),turtle.ycor(),comp,orientacao - inc,cor)
E pronto! O leitor atento notará que o if final se deve à necessidade de desenhar o último segmento da cauda. Por outro lado, note como controlamos a orientação da cauda, e como relacionamos a orientação dos segmentos e dos laços. Finalmente, a cor dos laços e dos segmentos é a mesma, mas é trivial fazer com que tenham cor diferente!

Depois de feito o programa, torna-se evidente que podemos simplificar o desenho dos segmentos:
def boneco(n, posx, posy, orientacao,inc,comp,lado,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.pencolor(cor)
    for i in range(n):
        if i % 2 == 0:
            turtle.setheading(orientacao + inc)
            turtle.forward(comp)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()-90,lado,cor)
        else:
            turtle.setheading(orientacao - inc)
            turtle.forward(comp)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()+90,lado,cor)
    if n % 2 == 0:
        turtle.setheading(orientacao + inc)
        turtle.forward(comp)
    else:
        turtle.setheading(orientacao - inc)
        turtle.forward(comp) 
E chega… ou talvez não!

Para terminar, e embora não fosse necessário, um pequeno programa para desenhar apenas uma cauda ondulante:
def cauda(n,posx,posy,comp,orienta_1,orienta_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.pencolor(cor) 
    for i in range(n):
        if i % 2 == 0 :
            seg(turtle.xcor(),turtle.ycor(),comp, orienta_1,cor)
        else:
            seg(turtle.xcor(),turtle.ycor(),comp, orienta_2,cor)
Note-se como controlamos a orientação dos segmentos.

sábado, 30 de setembro de 2017

Zen ou a Arte de Programar

(brincadeiras de um sábado de manhã)


Suponham que lhe pedem um programa capaz de desenhar o símbolo do Yin-Yang:
Por onde começar?? Olhando para a figura verificamos que é composta à base de circunferências e semi-circunferências a duas cores (preto e branco). Usando um princípio já nosso conhecido, tentemos simplificar a questão, esquecendo os círculos interiores e as cores:
def zen1(raio):
    turtle.setheading(90)
    turtle.circle(raio)
    turtle.circle(raio/2,-180)
    turtle.setheading(90)
    turtle.circle(raio/2,180) 
Executando o código é criado o seguinte:
Ao fazermos assim ficamos com o problema de colocar as cores… Com um pouco de reflexão chegamos a uma solução, baseada na ideia de separar a circunferência exterior em duas semi-circunferências. O cuidado a ter é com as orientações a cada momento da tartaruga. Aqui basta não esquecer o principio de que a tartaruga quando se movimenta vê sempre o centro à sua esquerda!
def zen11(raio):
    turtle.setheading(90)
    turtle.circle(raio,180)
    turtle.fillcolor('black')
    turtle.begin_fill()
    turtle.circle(raio/2,-180)
    turtle.setheading(270)
    turtle.circle(raio/2,180)
    turtle.circle(raio,-180)
    turtle.end_fill()
ao executar o programa obtemos:
Agora é “só” colocar os círculos pequenos. Uma vez mais basta saber definir os centros:
def zen12(raio):
    turtle.setheading(90)
    turtle.circle(raio,180)
    turtle.fillcolor('black')
    turtle.begin_fill()
    turtle.circle(raio/2,-180)
    turtle.setheading(270)
    turtle.circle(raio/2,180)
    turtle.circle(raio,-180)
    turtle.end_fill()
    # círculos pequenos
    # esquerdo
    turtle.setx(turtle.xcor() + raio/2 -raio/10)
    turtle.fillcolor('white')
    turtle.begin_fill()
    turtle.circle(raio/10)
    turtle.end_fill()
    # direito
    turtle.penup()
    turtle.setx(turtle.xcor()+ raio)
    turtle.pendown()
    turtle.fillcolor('black')
    turtle.begin_fill()
    turtle.circle(raio/10)  
    turtle.end_fill()
    turtle.hideturtle()    
Et voilá!

Mas será que é a única forma de resolver o problema? Será que ficamos satisfeitos??? Olhando para a solução acima o que podemos dizer? Bem, que há muita coisa “fixa”: a posição, a orientação, as cores. Embora estas duas últimas façam parte da definição clássica da imagem, podemos procurar libertar-mo-nos e permitir uma versão mais geral. Por outro lado, e talvez mais importante, o modo como construímos a solução usa componentes de baixo nível e não olha para a imagem como sendo composta por duas que se distinguem … pela posição, orientação e cores!!! Vamos tentar então uma segunda abordagem.
   
def zen(raio,x_cor,y_cor,orient, cor_1,cor_2):
    """
    Desenha uma componente.
    """
    # corpo principal
    vai_para_or(x_cor,y_cor,orient)
    turtle.fillcolor(cor_1)
    turtle.begin_fill()
    turtle.circle(raio,-180)
    #vai_para_or(x_cor,y_cor,orient)   
    turtle.circle(raio/2,-180)
    turtle.setheading(turtle.heading()+180)
    turtle.circle(raio/2,180)
    turtle.end_fill()
    # restaura
    vai_para_or(x_cor,y_cor,orient+90)
    # círculo pequeno  
    turtle.penup()
    turtle.forward(3*raio/2-raio/10)
    turtle.pendown()
    turtle.fillcolor(cor_2)
    turtle.setheading(orient)
    turtle.begin_fill()
    turtle.circle(raio/10)
    turtle.end_fill()
    turtle.hideturtle()
O código apresentado torna mais claro a importância da orientação em cada momento do desenho. O raio do círculo pequeno foi decidido que tenha um valor 10 vezes mais pequeno que o raio da semi-circunferência maior. O resultado quando escolhemos as cores vermelho e azul:
Agora podemos apresentar o nosso programa para o Yin-Yang:
   
def yin_yang(raio,posx,posy):
    zen(raio,posx,posy,90,'black','white')
    zen(raio,posx - 2*raio,posy,270, ‘white','black')
Com esta solução podemos então … brincar:
   
def zen_rose(raio,posx,posy,orientacao,cor_1, cor_2, num):
    for i  in range(num):
        zen(raio,posx,posy,orientacao,cor_1,cor_2)
        orientacao = orientacao + 360/num
Executando vem:
Mas podemos variar um pouco:
def zen_rose_2(raio,posx,posy,orientacao,cor_1, cor_2, num):
    for i  in range(num):
        if i % 2 == 0:
            zen(raio,posx,posy,orientacao,cor_1,cor_2)
        else:
            zen(raio,posx,posy,orientacao,cor_2,cor_1)
        orientacao = orientacao + 360/num
Ou criar objectos mais coloridos:
def zen_rose_tutti(raio,posx,posy,orientacao, num):
    turtle.colormode(255)
    for i  in range(num):
        cor_1 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        cor_2 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        zen(raio,posx,posy,orientacao,cor_1,cor_2)
        orientacao = orientacao + 360/num
E agora:
E como alguém disse: o céu é o limite! Invente!!!
def zen_voa(raio,posx,posy,orientacao, num):
    turtle.speed(10)
    turtle.colormode(255)
    for i  in range(1,num+1):
        cor_1 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        cor_2 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))        
        zen(raio,posx,posy,orientacao,cor_1,cor_2)
        #orientacao = orientacao + 360/num
        posx = posx + i * raio/5
        posy = posy + i * raio/5
 

Formas, Cores e outras coisas....

O módulo turtle da linguagem Python tem um conjunto vasto de operações que podem ser exceptuadas sobre objectos do tipo turtle e sobre um mundo 2D. Durante as aulas vimos alguns exemplos de comandos básicos e o que com eles podíamos fazer. Por exemplo, para desenhar um quadrado:
def quadrado(lado):
    for i in range (4):
        turtle.forward(lado)
        turtle.right(90)
Usando esta definição como componente podemos brincar um pouco. Por exemplo, desenhar um certo número de vezes um quadrado, mudando apenas a orientação:
def tarta_ernesto(posx,posy):
    # tartaruga ernesto
    turtle.shape("turtle")
    turtle.color("yellow")
    turtle.speed(10)
    turtle.pensize(2)
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    for i in range(36):
        quadrado(150)
        turtle.right(10)
    turtle.hideturtle()
Como se pode ver existem comandos para:

- definir a forma da tartaruga

definir a cor

definir a velocidade da tartaruga

definir a espessura do rasto

O resultado é ilustrado na figura.
Mudando o ciclo no interior do programa podemos conseguir outro tipo de formas:
def tarta_costa(posx,posy):
    #tartaruga costa
    turtle.shape("turtle")
    turtle.color("blue")
    turtle.speed(10)
    turtle.pensize(2)
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha    
    for i in range(400):
        turtle.forward(i)
        turtle.right(91)
    turtle.hideturtle()
Tal como está escrito, a única “coisa” que controlamos e podemos variar é a posição inicial. Visualmente:
Podemos usar a mesma ideia de base (uma forma básicas que é replicada….) para criar diferentes formas coloridas. Experimente você.

quarta-feira, 27 de setembro de 2017

Na aula foi pedido que escrevessem um programa que desenhasse espirais. Ficou claro, após alguma discussão, que o desenho iria depender de dois factores inter-relacionados: o tamanho do traço e o ângulo de viragem. Também é importante definir o número de “voltas” da espiral, claro, mas isso não causa problema de maior.

Daqui resulta um programa simples:
import turtle

def spiro(tam,k,ang,size):
    for i in range(size):
        turtle.forward(tam + k*i)
        turtle.right(ang)
    turtle.hideturtle()
Não é preciso pensarmos muito para perceber que os traços que vamos usar terão que ter o seu tamanho incrementado. Porquê? Bem, porque a não ser assim, em função do ângulo de viragem, em vez de uma espiral teremos repetições da mesma volta. E o ângulo? Se fôr muito pequeno vamos ter uma grande amplitude e dificuldade em girar 360 graus. Se fôr muito grande rapidamente os traços se intersectam. Mas o melhor é experimentar com valores concretos. Experimente tentar obter espirais com o aspecto dos das figuras. E muitas mais. Divirta-se!

sexta-feira, 22 de setembro de 2017

Mais uma vez...

Vamos recomeçar mais uma edição da cadeira de IPRP. Como no passado vou procurar deixar aqui elementos que podem ajudar à melhor compreensão da matéria e consequente melhoria do vosso desempenho.

Estejam atentos!

domingo, 19 de fevereiro de 2017

EXAME RECURSO - P1

Dado o programa
 
def xpto(lista):
    # ordena lista de modo decrescente
    for i in range(len(lista)):
        m = max(lista[i:])
        ind = lista.index(m)
        lista[i],lista[ind] = lista[ind], lista[i]
    return lista
pretende-se saber o que faz, como o faz e se, eventualmente, tem algum erro e como pode ser corrigido.

O programa recebe como entrada uma lista de números e devolve essa mesma lista ordenada de modo decrescente. Funciona percorrendo a lista da esquerda para a direita e na etapa i determinar a posição do maior elemento da sub-lista desde a posição i até ao final. De seguida esse elemento troca a sua posição com o elemento na posição i. Existe uma situação em que este algoritmo não funciona, quando existem elementos repetidos. Para corrigir, basta usar a instrução
 
ind = lista.index(m,i)
no lugar de
 
ind = lista.index(m)

domingo, 8 de janeiro de 2017

Teste 3 - TP2

Problema 2

Saber se temos prémio no euromilhões e o seu valor não é tarefa difícil. Na solução abaixo admitimos que vamos ter a chave correcta e a nossa chave representadas por uma lista de dois elementos. O primeiro, é uma lista de cinco números e, o segundo, uma lista de dois números (as estrelas). Por outro lado o valor dos prémios está armazenado num dicionário em que a chave é um tuplo (n1, n2) que traduz quantos números e estrelas acertámos e o valor é o prémio.
def premio_euro(dicio,chave_certa, minha_chave):
    # as chaves estão na forma [[5 números],[2 estrelas]]
    # verifica números
    num_certos = chave_certa[0]
    num_meus = minha_chave[0]
    conta_n = 0
    for num in num_meus:
        if num in num_certos:
            conta_n += 1
    # verifica estrelas
    est_certas = chave_certa[1]
    est_minhas = minha_chave[1] 
    conta_e = 0
    for est in est_minhas:
        if est in est_certas:
            conta_e += 1
    # calcula prémio
    return dicio.get((conta_n,conta_e),0)
Problema 3

Pretende-se normalizar os números guardados num ficheiro. Cada linha do ficheiro contém um nome e os números. A normalização é feita por linha, subtraindo a cada número a média dos valores dos números da linha e dividindo o resultado pelo respectivo desvio padrão.
import statistics

def normaliza_fich(fich_entrada, fich_saida):
    # abre ficheiros
    f_in = open(fich_entrada,'r',encoding='utf8')
    f_out = open(fich_saida,'w',encoding='utf8')
    # lê e normaliza por linha
    for linha in f_in:
        # recolhe nome e números
        linha = linha.strip().split()
        nome = linha[0]
        numeros = [int(num) for num in linha[1:]]
        # normaliza números
        num_normais = normaliza(numeros)
        # escreve resultado
        nova_linha = nome + ' '.join([str(num) for num in num_normais]) + ‘\n'
        f_out.write(nova_linha)
    # fecha ficheiros
    f_in.close()
    f_out.close()
    
def normaliza(numeros):
    media = statistics.mean(numeros)
    desvio_pad = statistics.stdev(numeros)
    return [(num - media)/desvio_pad for num in numeros]

A função auxiliar normaliza usa o módulo statistics para normalizar os números dados numa lista.

sábado, 7 de janeiro de 2017

Teste 3 - TP1

Problema 2

Num dicionários a diferentes chaves podemos ter associado o mesmo valor. O problema de calcular qual o valor mais frequente pode ser resolvido de forma simples do seguinte modo:
def moda(dicio):
    # inverte dicio
    novo_dicio = {}
    for c,v in dicio.items():
        novo_dicio[v] = novo_dicio.get(v,[]) + [c]
    # passa a lista ordenada
    lista_items = list(novo_dicio.items())
    mais_freq = (0,[])
    for val,lst in lista_items:
        if len(lst) > len(mais_freq[1]):
            mais_freq = (val,lst)
    # devolve o mais valor mais frequente
    return mais_freq    
Como os comentários indicam, a estratégia de solução passa por inverter o dicionário, converter para uma lista e depois calcular o elemento, i.e. o par (valor, lista das chaves com esse valor), mais frequente. Para os pythónicos apresentamos outra solução que recorre a funções anónimas:
def moda_b(dicio):
    # inverte dicio
    novo_dicio = {}
    for c,v in dicio.items():
        novo_dicio[v] = novo_dicio.get(v,[]) + [c]
    # passa a lista ordenada
    lista_items = list(novo_dicio.items())
    lista_items.sort(key=lambda x: len(x[1]),reverse=True)
    # devolve o mais valor mais frequente
    return lista_items[0]
A função anónima (lambda) é usada para o ordenamento ser feito de acordo com o tamanho do elemento na posição 1.

Problema 3

O problema envolvia um ficheiro em que cada linha é formado por um nome e números (>= 3). Pretende-se criar um novo ficheiro com o nome e a média dos números depois de retirar o menor e o maior.

Uma solução mágica:
def fich_media(fich_entrada,fich_saida):
    # modificar linha a linha
    # abre ficheiros
    f_in = open(fich_entrada,'r',encoding='utf8')
    f_out = open(fich_saida,'w',encoding='utf8')
    
    # trata por linha
    for linha in f_in:
        # escolhe numeros
        linha = linha.strip().split()
        nome = linha[0]
        numeros = [ int(num) for num in linha[1:]]
        numeros.sort()
        
        # calcula média
        media = sum(numeros[1:-1])/(len(numeros)-2)
        # escreve nova linha
        f_out.write(nome + str(media) + '\n')
    # fecha ficheiros
    f_in.close()
    f_out.close()
Dada a natureza do enunciado resolvemos natural tratar o problema linha a linha. Cada linha é partida e os seus números ordenados. Calculamos de seguida a média retirando o primeiro (o mais pequeno) e o último (o maior).