segunda-feira, 26 de outubro de 2015

Teste # 1 - TP2

Pergunta 2

Como encontrar os números perfeitos entree 1 e um dado n? Por número perfeito entende-se um número que é igual ao produto de dois inteiros iguais. Por exemplo, 25 = 5 * 5 é um número perfeito. Outra forma de definir seria dizer que é um número cuja raiz quadrada é um inteiro.

Uma solução muito simples será calcular os sucessivos produtos de um inteiro por si próprio filtrando os resultados menores ou iguais a n.
def perfeito(n):
 for i in range(1,n+1):
  prod = i * i
  if prod <= n:
   print(n)
Fácil, certo? Mas será que vale a pena repetir i ciclo n vezes?? Por exemplo, se n=100 é claro que basta testar os inteiros … até 10, ou seja,até à raiz quadrada de 100. Daí uma nova solução.
import math

def perfeito(n):
 sup = int(math.sqrt(n))
 for i in range(1,sup+1):
  prod = i * i
  if prod <= n:
   print(n)
E pronto. Notar apenas que temos que garantir que os argumento de range são números inteiros.

Pergunta 3

Esta pergunta é semelhante à colocada na TP1. Daí que apresentemos apenas a solução. O leitor deve referir-se ao texto anterior caso não entenda o que é apresentado. Dito isto, chamamos a atenção para o modo como conseguimos que a formação seja diferente, alterando apenas o modo de modificar a nova posição.
import turtle

def triangulo(lado,orientacao,pos_x,pos_y,cor):
    # inicializa tartaruga
    turtle.showturtle()
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orientacao)
    turtle.colormode(255)
    turtle.pencolor(cor)
    turtle.pendown()
    # desenha triangulo
    for i in range(3):
        turtle.forward(lado)
        turtle.left(120)
    turtle.hideturtle()
    
def boneco(lado,orientacao,pos_x,pos_y,cor,n):
    for i in range(n):
        triangulo(lado,orientacao,pos_x,pos_y,cor)
        pos_x = pos_x - lado//5
        pos_y = pos_y - lado//5
        orientacao = orientacao + 15
        lado = lado + 10

if __name__ == '__main__':
    #triangulo(40,45,25,50,(0,255,255))
    boneco(50,0,0,0,(0,0,255),10)
    turtle.exitonclick()

Teste #1 - TP1

Pergunta 2

Era pedido um programa que calculasse a percentagem de ocorrências de caras no lançamento de uma moeda ao ar. O número dos lançamentos era um parâmetro do programa.

Não era preciso pensar muito para perceber que: (a) vamos precisar de simular o lançamento da moeda um número fixo de vezes e, (b) contar o número de vezes em que ocorreu caras. Um modelo inicial de solução poderá ser:
def caras(n):
 # inicializa contagem
 for i in range(n):
  # lança moeda
  if #caras?:
   # actualiza contagem
 return #percentagem
Aprofundando um pouco mais a solução, torna-se claro que vamos precisar de um acumulador que conte o número de vezes em que saiu caras:
def caras(n):
 # inicializa contagem
 conta = 0
 for i in range(n):
  # lança moeda
  if #caras?:
   # actualiza contagem
   conta = conta + 1
 return conta/n * 100
E estamos perante o padrão ciclo - acumulador!
Simular o lançamento pode ser conseguido recorrendo ao módulo random e ao método randint.
import random 

def caras(n):
 # inicializa contagem
 conta = 0
 for i in range(n):
  # lança moeda
  resultado = random.randint(0,1)
  if resultado == 0:
   # actualiza contagem
   conta = conta + 1
 return conta/n * 100
Nesta solução admitimos que 0 significa caras. Também podíamos usar outra representação, usando agora o método choice.
import random 

def caras(n):
 # inicializa contagem
 conta = 0
 for i in range(n):
  # lança moeda
  resultado = random.choice(’caras’,’coroas’)
  if resultado == ‘caras’:
   # actualiza contagem
   conta = conta + 1
 return conta/n * 100
Pergunta 3

Este problema tinha duas partes. Primeiro, escrever um programa que permite desenhar um quadrado, tendo como parâmetros o tamanho do lado, a cor do traço, a orientação e a posição. Segundo, usar este programa como auxiliar para desenhar vários quadrados, alterando os parâmetros em cada um de modo a desenhar uma forma semelhante a um nautilus. Divididas as questões deste modo a primeira questão era trivial (e já resolvida nas aulas,…).
import turtle

def quadrado(lado,orientacao,pos_x,pos_y,cor):
    # inicializa tartaruga
    turtle.showturtle()
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orientacao)
    turtle.colormode(255)
    turtle.pencolor(cor)
    turtle.pendown()
    # desenha quadrado
    for i in range(4):
        turtle.forward(lado)
        turtle.right(90)
    turtle.hideturtle()
Como se pode ver, no início inicializamos a tartaruga de acordo com os parâmetros, para de seguida desenhar o quadrado. Não há muito mais que se possa dizer.

Quanto à segunda parte do problema só tínhamos que perceber que se trata de repetir o desenho do quadrado com diferente orientação, posição e tamanho. E foi para isso que escrevemos o primeiro programa…
def boneco(lado,orientacao,pos_x,pos_y,cor,n):
    for i in range(n):
        # Desenha Quadrado
        quadrado(lado,orientacao,pos_x,pos_y,cor)
        # Actualiza parâmetros
        pos_x = pos_x + lado//5
        pos_y = pos_y + lado//5
        orientacao = orientacao + 15
        lado = lado + 10
Muitos não entenderam o significado da expressão “utilize o primeiro programa como auxiliar”. Espero que com o exemplo concreto tenham agora percebido.

sábado, 17 de outubro de 2015

Erros Comuns (de novo)

Em textos anteriores, em novembro de 2011, descrevemos um conjunto de erros muito frequentes, uns mais graves do que outros, uns cometidos por novatos em programação, outros por programadores mais experientes. Aconselhamos os leitores que estão a fazer a sua formação inicial a passarem os olhos por esses posts. Vou agora referir dois erros graves cometidos por novatos, ligados aos conceitos de parâmetros formais e parâmetros reais.
def toto(n):
    n = 5
....
Para que serve o argumento (parâmetro formal n) se logo a seguir, no início do código da função, a associação é destruída??? Não serve para nada!
def toto(5):
....
Este erro radica no desconhecimento de que os parâmetros formais (argumentos das funções) são apenas nomes que durante a activação da função vão ficar associados a objectos.
Espero não encontrar estes erros no teste da próxima terça-feira...

Codifica e descodifica

[Problema 3.18 (dos apontamentos)] Um método muito simples para codificar/descodificar um texto baseia-se na ideia de usar uma chave de substituição. Uma variante básica do método consiste em substituir uma letra por outra que se encontre a uma certa distância dela. Por exemplo, se a distância for 2, então o c substitui o a, o d substitui o b e assim sucessivamente. Vamos escrever um programa que dado um texto e um inteiro que representa a distância, devolve o texto encriptado de acordo com essa distância.

Uma maneira de pensar a solução remete para o padrão ciclo-acumulador: usamos uma variável onde vai sendo acumulado o texto codificado, sendo que o processo de acumulação é guiado por um ciclo em que cada caractere do texto original é analisado sequencialmente.
def codifica(texto_normal,chave):
    """Codifica um texto pelo método de substituição. A chave é a distância
    para codificar. Exemplo: 'a' passa a 'c' se chave for 2."""
    texto_enc =''
    for car in texto_normal:
        texto_enc = texto_enc + substitui(car,chave)
    return texto_enc
Este programa funciona caso a função substitui fizer o pretendido: substituir um caractere pelo seu substituto de acordo com a chave. Podemos testar o programa desde que façamos uma implementação preliminar desta função auxiliar:
def substitui(car,chave):
 return ‘a’
Se testarmos o resultado será um texto do mesmo comprimento que o original formado apenas pelo caractere a. Nada de muito entusiasmante! No entanto, esta forma de proceder permite testar a lógica da nossa solução. Vamos então implementar a sério a função. A nossa solução usa o facto de cada caractere ter um código numérico. Por exemplo, o a tem como código 97. Existem duas funções em Python que nos vão ajudar: ord(car), fornece o código do car e chr(num) que dá o caractere que tem num como código.
def substitui(car,chave):
    return chr(ord(car)+chave)
E já está! Suponhamos que queremos também obter o original de um texto codificado de acordo com uma dada chave. Trivial:
def descodifica(texto_normal,chave):
    """Descodifica um texto pelo método de substituição. A codificação foi feita de acordo 
    com a seguinte estratégia. A chave é a distância
    para codificar. Exemplo: 'a' passa a 'c' se chave for 2."""
    texto_enc =''
    for car in texto_normal:
        texto_enc = texto_enc + substitui(car,-chave)
    return texto_enc
Como se pode verificar esta função é quase idêntica à sua inversa. A única coisa que mudou é que na chamada da substitui usamos o simétrico (-chave) como valor.

Esta solução é um pouco limitada e supõe que o alfabeto é infinito à esquerda e à direita. Com efeito, se tivermos um caractere que tem o código de valor máximo (ou mínimo) qual vai ser o seu substituto. Por outro lado, existem códigos que não correspondem a letras, mas antes a sinais ou caracteres de controlo. Que fazer? Em baixo apresentamos uma solução para estas questões. Passa por definir explicitamente o alfabeto, e usar o método de procura do índice de um caractere numa cadeia (alfabeto). Por outro lado, para codificar os caracteres nos extremos do alfabeto, usamos a aritmética módulo tamanho do alfabeto (operação %). Na prática, isto corresponde a termos os caracteres organizados de forma circular.
def codifica(texto_normal,chave):
    """Codifica um texto pelo método de substituição. A chave é a distância
    para codificar. Exemplo: 'a' passa a 'c' se chave for 2. Supõe que
    os caracteres são as 26 letras (minúsculas) do alfabeto  e o branco."""
    alfabeto = 'abcdefghijklmnopqrstuvwxyz '
    texto_encriptado = ''
    for car in texto_normal:
        novo_codigo = (alfabeto.index(car) + chave) % len(alfabeto)
        novo_car = alfabeto[novo_codigo]
        texto_encriptado = texto_encriptado + novo_car
    return texto_encriptado
Notar que nesta solução não usamos nenhuma função auxiliar. Mas isso pode ser feito sem problemas, considerando que é a função substitui que sabe qual é o alfabeto. Fica para o leitor a escrita do equivalente para descodificar.

sexta-feira, 16 de outubro de 2015

Tiro ao alvo

Já todos jogamos com arco e flechas. O objectivo é acertar num alvo colocado a uma certa distância. A nossa ideia é simular com um programa de computador o treino de um jogador. Para começar vamos supor que apenas se pretende calcular a percentagem de vezes que acerta no alvo. Assumimos que o alvo tem a forma de um circulo. Podemos começar com uma primeiro esboço do programa.
def tiro_alvo(raio, num):
 # inicialização
 for i in range(num):
  # atira flecha
  # verifica se acertou
        pass
 # calcula percentagem de acertos
Temos assim que resolver vários subproblemas. Comecemos pelo mais simples: calcular a percentagem. Isso envolve contar os acertos e dividir pelo número total de lançamentos.
def tiro_alvo(raio, num):
 # inicialização
 conta = 0
 for i in range(num):
  # atira flecha
  # verifica se acertou
 # calcula percentagem de acertos
 return conta / num
Estamos perante o padrão ciclo-acumulador.

Como simular o lançamento da flecha? Uma hipótese simples é escolher de modo aleatório as coordenadas do ponto onde vai incidir a flecha. Recorremos ao módulo random.
import random

def tiro_alvo(raio, num):
 # inicialização
 conta = 0
 for i in range(num):
  # atira flecha
  x = random.randint(-raio,raio)
  y = random.randint(-raio,raio)
  # verifica se acertou
 # calcula percentagem de acertos
 return conta / num
Na solução acima escolhemos considerar que os pontos onde vai cair a flecha se encontra num quadrado de lado 2*raio e centrado no ponto (0,0). Falta agora verificar se acertamos no alvo ou não. A solução passa por calcular a distância (euclidiana) do ponto ao centro. Se for maior do que o raio, então estará fora do alvo.
import random
import math

def tiro_alvo(raio, num):
 # inicialização
 conta = 0
 for i in range(num):
  # atira flecha
  x = random.randint(-raio,raio)
  y = random.randint(-raio,raio)
  # verifica se acertou
  if math.sqrt(x**2 + y**2) < raio:
   conta = conta + 1
 # calcula percentagem de acertos
 return conta / num
E pronto, chegámos ao fim. Como se observou é possível construir uma solução por etapas a partir de um modelo geral.

Podemos tornar o problema ligeiramente diferente, considerando um alvo mais realista contendo várias zonas, sendo que cada uma tem um valor associado: vale tanto mais o nosso lançamento quanto mais perto estiver do centro. Na solução que vamos apresentar considerámos que existem três zonas. Por outro lado, agora o resultado pretendido é a soma de pontos obtido por um jogador.
def tiro_ao_arco_2(raio_1,raio_2,raio_3,num):
    pontos = 0
    for i in range(num):
        # atira flecha
        x = random.randint(-raio_1,raio_1)
        y = random.randint(-raio_1,raio_1)
        dist_centro = math.sqrt(x**2 + y**2)
        if dist_centro <= raio_3:
            pontos = pontos + 50
        elif dist_centro <= raio_2:
            pontos = pontos + 20
        elif dist_centro <= raio_3:
            pontos = pontos + 10
        else:
            pontos = pontos - 5
    return pontos
A partir daqui pode imaginar as variantes que quiser, incluindo desenhar, usando o turtle o alvo e a localização das flechas…

quinta-feira, 15 de outubro de 2015

Uma tartaruga na prisão

Nas aulas vimos como podíamos resolver o problema de ter uma tartaruga num passeio aleatório mas sem poder sair de uma prisão circular. A nossa solução, muito simples, baseou-se no uso das primitivas distance(x,y) e towards(x,y). A primeira, permite determinar a distância entre a posição actual da tartaruga e a posição (x,y). A segunda, calcula o ângulo entre a linha que une a posição corrente da tartaruga e (x,y) com o eixo dos X. Eis o código básico:
import turtle
import random

def random_walk_prison(raio,pos_x,pos_y,ang,step,num):
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(ang)
    turtle.pendown()
    for i in range(num):
        if turtle.distance(pos_x, pos_y) > raio:
            turtle.setheading(turtle.towards(pos_x,pos_y))
        turtle.forward(step)
Esta solução permite controlar a dimensão da prisão (raio), a posição inicial da tartaruga (pos_x e pos_y), o ângulo inicial (ang), a dimensão de cada passo (step) e o número de passos da tartaruga (num). O código é fácil de entender: enquanto não tivermos esgotado o número de passos concedido, avançamos. Se ultrapassarmos o limite autorizado voltamos para trás na direção de onde partimos. Notar que a primitiva towards não altera a orientação, pelo que é necessário usar o setheading.

Se executarmos este programa o resultado não é muito entusiasmante: a tartaruga anda para trás e para a frente ao longo de uma linha. Tudo menos um caminhar aleatório. Vamos por isso pensar em melhorar o código. Começamos por coisas simples: mudar a forma e a cor da tartaruga e retirar o rasto.
def random_walk_prison_1(raio,pos_x,pos_y,ang,step,num):
    turtle.setheading(ang)
    turtle.shape('turtle') 
    turtle.color('blue')
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    for i in range(num):
        if turtle.distance(pos_x, pos_y) > raio:
            turtle.setheading(turtle.towards(pos_x,pos_y))
        turtle.forward(step)
Não se pode dizer que esteja muito melhor. Vamos então introduzir alguma variabilidade no caminhar da tartaruga, fazendo com que se movimente sem ser apenas para a frente e para trás.
def random_walk_prison_2(raio,pos_x,pos_y,ang,step,num):
    turtle.setheading(ang)
    turtle.shape('turtle') 
    turtle.color('blue')    
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    for i in range(num):
        if turtle.distance(pos_x, pos_y) > raio:
            offset = random.randint(-10,10)
            turtle.setheading(turtle.towards(pos_x,pos_y) + offset)
        turtle.forward(step)
Como se pode ver recorremos ao método randint do módulo random. Já nos parece mais razoável, mas podemos melhorar ainda o programa, desenhando a prisão. No nosso caso desenhamos uma circunferência de raio definido pelo utilizador. Também alteramos a espessura do traço.
def random_walk_prison_3(raio,pos_x,pos_y,ang,step,num):
    # prisão
    turtle.penup()
    turtle.goto(pos_x,pos_y-raio-step)
    turtle.pendown()
    turtle.width(5)
    turtle.pencolor('red')
    turtle.circle(raio+step)
    # tartaruga
    turtle.setheading(ang)
    turtle.shape('turtle') 
    turtle.color('blue')    
    turtle.penup()
    turtle.hideturtle()
    turtle.goto(pos_x,pos_y)
    turtle.showturtle()
    for i in range(num):
        if turtle.distance(pos_x, pos_y) > raio:
            offset = random.randint(-10,10)
            turtle.setheading(turtle.towards(pos_x,pos_y) + offset)
        turtle.forward(step)
E pronto… ou talvez não. Porque é que a tartaruga tem que ter o centro da prisão sempre como referência?? Não tem, pois claro! Então vamos alterar isso.
def random_walk_prison_4(raio,pos_x,pos_y,ang,step,num):
    # prisão
    turtle.penup()
    turtle.goto(pos_x,pos_y-raio-step)
    turtle.pendown()
    turtle.width(5)
    turtle.pencolor('red')
    turtle.circle(raio+step)
    # tartaruga
    turtle.setheading(ang)
    turtle.shape('turtle') 
    turtle.color('blue')    
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    for i in range(num):
        if turtle.distance(pos_x, pos_y) + step >= raio:
            offset = random.randint(-10,10)
            turtle.setheading(turtle.heading() + 180 + offset )
            print(turtle.heading())
        turtle.forward(step)
Note como mandamos para trás a tartaruga. A partir daqui … use a sua imaginação.