domingo, 27 de outubro de 2013

Olhar e ver (III)

Já por várias vezes referi a importância de pegar num problema complexo e dominar essa complexidade dividindo o problema inicial em sub-problemas mais simples e/ou começando por resolver um problema semelhante mas mais simples que depois se adapta para resolver a questão inicial.
Isto é importante, claro, mas de nada serve se não tivermos conhecimentos sobre o domínio do problema (por exemplo, pode obrigar a usar conhecimentos de trigonometria).
Mas podemos saber muito de técnicas de resolução de problemas, ter conhecimentos sobre o domínio do problema e mesmo assim não resolver o problema porque não conhecemos a linguagem de programação.


Assim para programar bem são precisas, pelo menos três coisas: dominar a linguagem de programação, ter conhecimentos sobre o domínio do problema e dominar a arte de resolução de problemas. Faltando uma delas e o resultado nunca será bom, se é que se chega a algum resultado...


Neste post vou falar sobre como o modo de decompor um problema em sub-problemas pode ter várias soluções e se liga aos outros aspectos. Suponhamos que queremos desenhar a figura seguinte:
Numa primeira abordagem admitamos que o que eu vejo são segmentos de recta ligados entre si. Nesta perspectiva é fundamental ter uma primitiva que me permita desenhar segmentos de recta conhecidos os seus pontos extremos. Não é um problema difícil:
1.def linha(x_1,y_1, x_2,y_2):
2.    """ Desenha uma linha entre dois pontos."""
3.    # posiciona-se
4.    turtle.pu()
5.    turtle.goto(x_1,y_1)
6.    turtle.pd()
7.    # Desenha
8.    turtle.goto(x_2,y_2)
Precisei de usar conhecimentos básicos do modo turtle. Agora só necessito de identificar os oito pontos do desenho e desenhar os doze segmentos que unem alguns entre si.
Feito isso o resto é trivial do ponto de vista da programação. Mas identificar os pontos pode não ser simples se não tivermos alguma conhecimento de geometria. Mas apresentemos primeiro o código.
01.def bloco(posx, posy,lado_1, lado_2):
02.    """ Desenha um bloco geométrico."""
03.    # Auxiliares
04.    passo_x = lado_2 * math.cos(math.pi/6)
05.    passo_y = lado_2 * math.sin(math.pi/6)
06.    # Desenho
07.    linha(posx,posy, posx+lado_1, posy)
08.    linha(posx+lado_1, posy, posx+ lado_1, posy + lado_2)
09.    linha(posx+ lado_1, posy + lado_2,posx, posy + lado_2)
10.    linha(posx, posy + lado_2, posx, posy)
11.     
12.    
13.    linha(posx + passo_x,posy + passo_y, posx + passo_x+lado_1, posy + posy+passo_y)
14.    linha(posx+passo_x+lado_1, posy+posy+passo_y, posx+passo_x+ lado_1, posy+posy+passo_y + lado_2)
15.    linha(posx+passo_x+ lado_1, posy+posy+passo_y + lado_2,posx+passo_x, posy+posy+passo_y + lado_2)
16.    linha(posx+passo_x, posy+posy+passo_y + lado_2, posx+passo_x, posy+posy+passo_y)
17.     
18.    linha(posx, posy, posx+passo_x, posy + passo_y)
19.    linha(posx, posy+lado_2, posx+passo_x , posy+lado_2+passo_y)
20.    linha(posx + lado_1, posy, posx+lado_1+passo_x , posy+passo_y)
21.    linha(posx + lado_1, posy + lado_2, posx+lado_1+passo_x , posy+lado_2+passo_y)
22.     
23.    turtle.hideturtle()
A questão central, pelo menos para mim, foi a de definir o que designei por passo_x e passo_y que me obriga a saber um pouco de trigonometria. Temos que convir que o programa além de extenso não é muito legível. Talvez se possa fazer melhor no que diz respeito a este segundo aspecto, passando do conceito de coordenadas para o conceito de ponto. Fazendo isso chegamos a uma nova solução.
01.def linha_b(p_1, p_2):
02.    """ Desenha uma linha entre dois pontos."""
03.    # posiciona-se
04.    turtle.pu()
05.    turtle.goto(p_1[0],p_1[1])
06.    turtle.pd()
07.    # Desenha
08.    turtle.goto(p_2[0],p_2[1])
09.     
10.def bloco_b(posx, posy,lado_1, lado_2, angulo):
11.    """ Desenha um bloco geométrico."""
12.    # Auxiliares
13.    passo_x = lado_2 * math.cos(angulo)
14.    passo_y = lado_2 * math.sin(angulo)
15.    # Pontos
16.    p_1 = (posx, posy)
17.    p_2 = (posx + lado_1, posy)
18.    p_3 = (posx + lado_1 + passo_x,posy + passo_y)
19.    p_4 = (posx + passo_x, posy+passo_y)
20.    p_5 = (posx, posy+lado_2)
21.    p_6 = (posx + lado_1, posy + lado_2)
22.    p_7 = (posx + lado_1 + passo_x, posy + lado_2 + passo_y)
23.    p_8 = (posx + passo_x, posy + lado_2 + passo_y)
24.     
25.    # Desenho
26.    linha_b(p_1, p_2)
27.    linha_b(p_2, p_3)
28.    linha_b(p_3, p_4)
29.    linha_b(p_4, p_1)
30.     
31.   
32.    linha_b(p_5, p_6)
33.    linha_b(p_6, p_7)
34.    linha_b(p_7, p_8)
35.    linha_b(p_8, p_5)
36.     
37.    linha_b(p_1, p_5)
38.    linha_b(p_2, p_6)
39.    linha_b(p_4, p_8)
40.    linha_b(p_3, p_7)
41.     
42.     
43.    turtle.hideturtle()
Penso que estamos de acordo de que se trata de uma solução mais limpa. Notar que aproveitámos as oportunidade para acrescentar como parâmetro formal o ângulo que determina o valor dos passos (ver figura).
Mas voltemos a olhar para a figura e admitamos que afinal o que vemos são dois rectângulos ligados por segmentos. Vamos de novo construir as respectivas primitivas.
01.def rectangulo(posx, posy, lado_1, lado_2):
02.    """ Desenha um rectangulo com o canto inferior esquerdo em (posx, posy)."""
03.    # Posiciona-se
04.    turtle.pu()
05.    turtle.goto(posx,posy)
06.    turtle.pd()
07.    # desenha
08.    for i in range(2):
09.        turtle.fd(lado_1)
10.        turtle.left(90)
11.        turtle.fd(lado_2)
12.        turtle.left(90)
13.    turtle.hideturtle()
14.     
15.def linha(x_1,y_1, x_2,y_2):
16.    """ Desenha uma linha entre dois pontos."""
17.    # posiciona-se
18.    turtle.pu()
19.    turtle.goto(x_1,y_1)
20.    turtle.pd()
21.    # Desenha
22.    turtle.goto(x_2,y_2)
Agora, de novo, a questão que resta resolver é a gestão dos pontos.
01.def main_2(posx, posy, lado_1, lado_2):
02.    """ Desenha o bloco."""
03.    # auxiliares
04.    canto_2_x = lado_2 * math.cos(math.pi/6)
05.    canto_2_y = lado_2 * math.sin(math.pi/6)
06.    # rectangulos
07.    rectangulo(posx, posy, lado_1, lado_2)
08.    rectangulo(canto_2_x, canto_2_y, lado_1, lado_2)
09.    # linhas
10.    linha(posx, posy, canto_2_x,canto_2_y)
11.    linha(posx, posy+lado_2, canto_2_x,canto_2_y + lado_2)
12.    linha(posx + lado_1, posy, canto_2_x + lado_1,canto_2_y)
13.    linha(posx + lado_1, posy+lado_2, canto_2_x + lado_1,canto_2_y + lado_2)
E cá está muito claro: dois rectângulos e quatro segmentos. Mas como somos esquisitos,um dia olhando de novo para a figura achámos que afinal tudo o que víamos eram degenerações de losangos (degeneração no mesmo sentido de que um rectângulo é um quadrado degenerado...).

Vamos então resolver a questão de desenhar a dita forma.
01.def quase_losango(posx, posy, lado_1, lado_2, angulo, orientacao):
02.    # Posiciona-se
03.    turtle.pu()
04.    turtle.goto(posx,posy)
05.    turtle.pd()
06.    turtle.setheading(orientacao)
07.    # desenha
08.    for i in range(2):
09.        turtle.fd(lado_1)
10.        turtle.left(angulo)
11.     
12.        turtle.fd(lado_2)
13.        turtle.left(180-angulo)
14.    turtle.hideturtle()
Isto resolvido, vamos ter o desenho de quatro quase-losangos (na realidade dois são mesmo losangos...).

01.def main_3(posx, posy, lado_1, lado_2):
02.    """ Desenha o bloco."""
03.    # auxiliares
04.    canto_2_x = lado_2 * math.cos(math.pi/6)
05.    canto_2_y = lado_2 * math.sin(math.pi/6)
06.    # quase losangos :pela ordem maior-frente, menor-esquerdo, menor-direito, maior-trás
07.    quase_losango(posx, posy, lado_1, lado_2, 30,0)
08.    quase_losango(posx, posy, lado_2, lado_2, 60,30)
09.    quase_losango(posx+lado_1, posy, lado_2, lado_2, 60,30)
10.    quase_losango(posx, posy+lado_2, lado_1, lado_2, 30,0)
Veja como tudo se simplificou. Mas não se esqueça de importar os módulos turtle e math! Agora pode dedicar-se a embelezar a solução. Por exemplo, colorindo-a...

Moral da História: para diferentes modos de olhar e ver, diferentes soluções. Todas são iguais (no produto final), mas há umas mais iguais do que outras. É muito importante aprender a ver e isso treina-se. Pode começar a treinar com as duas figuras abaixo. O que vê?

Sem comentários:

Enviar um comentário