quarta-feira, 6 de novembro de 2013

Ai, que é tão parecido com... (II)

Este blogue dá suporte à disciplina de IPRP e, naturalmente, preocupa-se com discutir questões de programação, a um nível introdutório, e com métodos de resolução de problemas. Este último aspecto tem contornos gerais (já discutidos num post anterior de Outubro de 2009, intitulado “Resolução de Problemas”), mas devemos procurar saber como é que os princípios genéricos de resolução de problemas se pode aplicar à programação. Neste post vou discorrer um pouco sobre como programar por analogia. Para tal vou socorrer-me do problema da pirâmide de números já abordado, cujo programa aqui retomo.
def piramide(n):
    for i in range(1, n+1):
        # posiciona
        print(4*(n-i)* ' ', end= ' ')
        # desenha descendente
        for j in range(i,0,-1):
            print('%3d' % j , end = ' ')
            # desenha ascendente
        for j in range(2,i+1):
            print('%3d' % j, end=' ')
        # muda de linha
        print()
Quando executado para n igual a 4 obtemos uma bela pirâmide:
Vamos supor agora que me pedem um outro tipo de pirâmide:
A pergunta a que temos que responder, uma vez recordados de já ter visto um problema parecido é: qual a diferença? Olhando para as suas imagens torna-se óbvio que não há diferença na forma mas apenas no conteúdo: os números são diferentes e para além disso temos uma sequência ascendente primeiro e depois uma descendente. Vejamos esta última questão primeiro. Invertemos a ordem dos dois ciclos interiores e adaptamos o range à nova situação.
def piramide_2(n):
    for i in range(1, n+1):
        # posiciona
        print(4*(n-i)* ' ', end= ' ')
        # desenha ascendente
        for j in range(1,i+1):
            print('%3d' % j, end=' ')        
        # desenha descendente
        for j in range(i-1,0,-1):
            print('%3d' % j , end = ' ')
        # muda de linha
        print()
Correndo o programa, de novo para n igual a 4, obtemos:
Já não está muito mal. Falta apenas a questão dos números. Se notarmos que a sequência envolve potências de 2, chegamos facilmente à solução por alteração do que é impresso.
def piramide_2(n):
    for i in range(1, n+1):
        # posiciona
        print(4*(n-i)* ' ', end= ' ')
        # desenha ascendente
        for j in range(1,i+1):
            print('%3d' % 2**(j-1), end=' ')        
        # desenha descendente
        for j in range(i-1,0,-1):
            print('%3d' %  2**(j-1) , end = ' ')
        # muda de linha
        print()
Não foi difícil passar de uma solução a outra pois o domínio era o mesmo. Mas admitamos que nos pedem para desenhar outro tipo de pirâmide: uma pirâmide de quadrados como a da figura (mas podendo ter qualquer altura,....):
Uma vez mais a pergunta: que diferença para os casos anteriores? Também aqui a forma é a mesma, alterando-se apenas o conteúdo. Agora cada linha é formada por uma sequência de quadrados idênticos. Não há valores que sobem e descem de acordo com uma regra, não, é tudo igual. Daqui resulta a mesma estrutura de solução, formada por um ciclo for e em que no seu interior temos que desenhar uma linha seguido de uma mudança de linha. O desenho da linha tem dois aspectos: ir para a posição inicial e desenhar a linha. Neste caso há apenas um ciclo for interior.
def pir_quadrados(n,posx, posy,lado):
    for i in range(1, n+1):
        # desenha linha i
        # -- (1) posiciona              
        # -- (2) desenha
        for j in range(1,i+1):
     pass
        # muda de linha
    turtle.hideturtle()
Este esqueleto de solução já tem alguns elementos que derivam da opção por turtle para desenhar os quadrados e também que os parâmetros formais da definição envolvem para além do número de linhas também a posição de referência para o desenho da pirâmide e o tamanho do lado do quadrado. Deixemos de lado por agora na questão da posição e da mudança de linha e fixemo-nos na questão do desenho. Na linha i temos que desenhar i quadrados. Não precisamos de muito tempo para perceber que depois de desenhar um quadrado temos que avançar uma distância igual ao comprimento do lado para desenhar o quadrado seguinte. Logo.
def pir_quadrados(n,posx, posy,lado):
    for i in range(1, n+1):
        # desenha linha i
        # posiciona
        # desenha
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
        # muda de linha
    turtle.hideturtle()
O código depende de uma definição auxiliar para desenhar cada quadrado individualmente. É essa definição que usada o número de vezes ditado pela ordem da linha. É um código que já encontrámos por diversas vezes no passado:
def quadrado(posx, posy,lado):
    turtle.showturtle()
    # posicioan
    turtle.penup()
    turtle.goto(posx, posy)
    turtle.pendown()
    # desenha
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.hideturtle()
E como mudamos de linha??? Bem, temos que nos posicionar “à esquerda”, na posição de referência para o eixo dos xx e baixar o valor no eixo dos yy de uma quantidade igual ao lado.
def pir_quadrados(n,posx, posy,lado):
    for i in range(1, n+1):
        # desenha linha i
        # posiciona
        # desenha
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
        # muda de linha
        turtle.penup()
        turtle.goto(posx,turtle.ycor()-lado)
        turtle.pendown()
    turtle.hideturtle()
Notar que subtraímos no caso do y pois existe a vontade de fazer crescer a pirâmide para baixo. Vamos executar o programa.
Como era de prever não vemos nenhuma pirâmide pois não resolvemos o problema do posicionamento inicial em cada linha. Mas não é muito diferente do caso das pirâmides dos números, só que aqui temos que fazer avançar ao longo do eixo dos xx e do eixo dos yy. Mas quanto? No caso do y não é difícil: para a linha i temos ir para (n-i)*lado - posy. Se olharmos para a figura de novo vemos que numa dada linha o deslocamento ao longo do eixo dos xx em relação à anterior é de metade do lado. Daí o resultado final:
def pir_quadrados(n,posx, posy,lado):
    for i in range(1, n+1):
        # desenha linha i
        # posiciona
        turtle.penup()
        turtle.goto(posx+(n-i)*lado/2, (n-i)*lado - posy)
        turtle.pendown()      
        # desenha
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
        # muda de linha
        turtle.penup()
        turtle.goto(posx,turtle.ycor()-lado)
        turtle.pendown()
    turtle.hideturtle()
Et voilá!

Mas, esperem. Quando eu mudo de linha o valor de y é actualizado, certo? Correcto. então porque é que no posicionamento me estou a preocupar em redefinir esse valor? Faz sentido!
def pir_quadrados_2(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha linha i
        # posiciona
        turtle.penup()
        turtle.setx(posx+(n-i)*lado/2)
        turtle.pendown()      
        # desenha
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
        # muda de linha
        turtle.penup()
        turtle.goto(posx,turtle.ycor()-lado)
        turtle.pendown()
    turtle.hideturtle()
Basta passar de um goto para um setx. Uff!

Mas, se eu simplifico o posicionamento porque faço isso nsa mudança de linha... não podia fazer ao contrário, ou seja, manter a actualização de y no posicionamento e simplificar na mudança de linha? Tem lógica e está bem observado.
def pir_quadrados_3(n,posx, posy,lado):
    for i in range(1,n+1):
        # desenha linha i
        # posiciona
        turtle.penup()
        turtle.goto(posx+(n-i)*lado/2, (n-i)*lado - posy)
        turtle.pendown()      
        # desenha
        for j in range(1,i+1):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
        # muda de linha
        turtle.penup()
        turtle.setx(posx)
        turtle.pendown()
    turtle.hideturtle()
Então afinal em que ficamos? Bom numa conclusão simples: os dois sub-problemas estão ligados entre si. Isso acontece com muita frequência e nessa altura temos que ver como é que jogam um com o outro. Bom. Agora sim, é tempo de encerrar este post. Ou talvez não... Lembrei-me agora de um outro problema que ocorre com frequência em jogos e simulações. Desenhar uma bela grelha colorida como a da figura.
Um... estamos a pensar todos o mesmo, não é verdade? Que diferença em relação aos problemas anteriores? Pensemos no óbvio, na pirâmide de quadrados. Não há problema de posicionamento diferenciado por linha e todas as linhas são iguais. Queremos adaptação mais simples???
def grelha(n,posx, posy,lado,cor):
    # inicialização
    turtle.pencolor(cor)
    for i in range(n):
        # desenha linha i    
        for j in range(n):
            quadrado(turtle.xcor(),turtle.ycor(), lado)
            turtle.setx(turtle.xcor()+lado)
        # muda de linha
        turtle.penup()
        turtle.goto(posx,turtle.ycor()-lado)
        turtle.pendown()
    turtle.hideturtle()
Bastou incluir o parâmetro formal para a cor, ter os dois ciclos executados o mesmo número de vezes, e não me preocupar com o posicionamento!!!

Elementar!

(Porque não pensar em variantes destes problemas, ou em usar a grelha final para colocar tartarugas a passear sobre a dita??? A imaginação é o limite.)

Sem comentários:

Enviar um comentário