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

Sem comentários:

Enviar um comentário