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.
01.def piramide(n):
02.    for i in range(1, n+1):
03.        # posiciona
04.        print(4*(n-i)* ' ', end= ' ')
05.        # desenha descendente
06.        for j in range(i,0,-1):
07.            print('%3d' % j , end = ' ')
08.            # desenha ascendente
09.        for j in range(2,i+1):
10.            print('%3d' % j, end=' ')
11.        # muda de linha
12.        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.
01.def piramide_2(n):
02.    for i in range(1, n+1):
03.        # posiciona
04.        print(4*(n-i)* ' ', end= ' ')
05.        # desenha ascendente
06.        for j in range(1,i+1):
07.            print('%3d' % j, end=' ')       
08.        # desenha descendente
09.        for j in range(i-1,0,-1):
10.            print('%3d' % j , end = ' ')
11.        # muda de linha
12.        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.
01.def piramide_2(n):
02.    for i in range(1, n+1):
03.        # posiciona
04.        print(4*(n-i)* ' ', end= ' ')
05.        # desenha ascendente
06.        for j in range(1,i+1):
07.            print('%3d' % 2**(j-1), end=' ')       
08.        # desenha descendente
09.        for j in range(i-1,0,-1):
10.            print('%3d' %  2**(j-1) , end = ' ')
11.        # muda de linha
12.        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.
01.def pir_quadrados(n,posx, posy,lado):
02.    for i in range(1, n+1):
03.        # desenha linha i
04.        # -- (1) posiciona             
05.        # -- (2) desenha
06.        for j in range(1,i+1):
07.     pass
08.        # muda de linha
09.    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.
01.def pir_quadrados(n,posx, posy,lado):
02.    for i in range(1, n+1):
03.        # desenha linha i
04.        # posiciona
05.        # desenha
06.        for j in range(1,i+1):
07.            quadrado(turtle.xcor(),turtle.ycor(), lado)
08.            turtle.setx(turtle.xcor()+lado)
09.        # muda de linha
10.    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:
01.def quadrado(posx, posy,lado):
02.    turtle.showturtle()
03.    # posicioan
04.    turtle.penup()
05.    turtle.goto(posx, posy)
06.    turtle.pendown()
07.    # desenha
08.    for i in range(4):
09.        turtle.forward(lado)
10.        turtle.left(90)
11.    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.
01.def pir_quadrados(n,posx, posy,lado):
02.    for i in range(1, n+1):
03.        # desenha linha i
04.        # posiciona
05.        # desenha
06.        for j in range(1,i+1):
07.            quadrado(turtle.xcor(),turtle.ycor(), lado)
08.            turtle.setx(turtle.xcor()+lado)
09.        # muda de linha
10.        turtle.penup()
11.        turtle.goto(posx,turtle.ycor()-lado)
12.        turtle.pendown()
13.    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:
01.def pir_quadrados(n,posx, posy,lado):
02.    for i in range(1, n+1):
03.        # desenha linha i
04.        # posiciona
05.        turtle.penup()
06.        turtle.goto(posx+(n-i)*lado/2, (n-i)*lado - posy)
07.        turtle.pendown()     
08.        # desenha
09.        for j in range(1,i+1):
10.            quadrado(turtle.xcor(),turtle.ycor(), lado)
11.            turtle.setx(turtle.xcor()+lado)
12.        # muda de linha
13.        turtle.penup()
14.        turtle.goto(posx,turtle.ycor()-lado)
15.        turtle.pendown()
16.    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!
01.def pir_quadrados_2(n,posx, posy,lado):
02.    for i in range(1,n+1):
03.        # desenha linha i
04.        # posiciona
05.        turtle.penup()
06.        turtle.setx(posx+(n-i)*lado/2)
07.        turtle.pendown()     
08.        # desenha
09.        for j in range(1,i+1):
10.            quadrado(turtle.xcor(),turtle.ycor(), lado)
11.            turtle.setx(turtle.xcor()+lado)
12.        # muda de linha
13.        turtle.penup()
14.        turtle.goto(posx,turtle.ycor()-lado)
15.        turtle.pendown()
16.    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.
01.def pir_quadrados_3(n,posx, posy,lado):
02.    for i in range(1,n+1):
03.        # desenha linha i
04.        # posiciona
05.        turtle.penup()
06.        turtle.goto(posx+(n-i)*lado/2, (n-i)*lado - posy)
07.        turtle.pendown()     
08.        # desenha
09.        for j in range(1,i+1):
10.            quadrado(turtle.xcor(),turtle.ycor(), lado)
11.            turtle.setx(turtle.xcor()+lado)
12.        # muda de linha
13.        turtle.penup()
14.        turtle.setx(posx)
15.        turtle.pendown()
16.    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???
01.def grelha(n,posx, posy,lado,cor):
02.    # inicialização
03.    turtle.pencolor(cor)
04.    for i in range(n):
05.        # desenha linha i   
06.        for j in range(n):
07.            quadrado(turtle.xcor(),turtle.ycor(), lado)
08.            turtle.setx(turtle.xcor()+lado)
09.        # muda de linha
10.        turtle.penup()
11.        turtle.goto(posx,turtle.ycor()-lado)
12.        turtle.pendown()
13.    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