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:
def linha(x_1,y_1, x_2,y_2):
    """ Desenha uma linha entre dois pontos."""
    # posiciona-se
    turtle.pu()
    turtle.goto(x_1,y_1)
    turtle.pd()
    # Desenha
    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.
def bloco(posx, posy,lado_1, lado_2):
    """ Desenha um bloco geométrico."""
    # Auxiliares
    passo_x = lado_2 * math.cos(math.pi/6)
    passo_y = lado_2 * math.sin(math.pi/6)
    # Desenho
    linha(posx,posy, posx+lado_1, posy)
    linha(posx+lado_1, posy, posx+ lado_1, posy + lado_2)
    linha(posx+ lado_1, posy + lado_2,posx, posy + lado_2)
    linha(posx, posy + lado_2, posx, posy)
    
   
    linha(posx + passo_x,posy + passo_y, posx + passo_x+lado_1, posy + posy+passo_y)
    linha(posx+passo_x+lado_1, posy+posy+passo_y, posx+passo_x+ lado_1, posy+posy+passo_y + lado_2)
    linha(posx+passo_x+ lado_1, posy+posy+passo_y + lado_2,posx+passo_x, posy+posy+passo_y + lado_2)
    linha(posx+passo_x, posy+posy+passo_y + lado_2, posx+passo_x, posy+posy+passo_y)
    
    linha(posx, posy, posx+passo_x, posy + passo_y)
    linha(posx, posy+lado_2, posx+passo_x , posy+lado_2+passo_y)
    linha(posx + lado_1, posy, posx+lado_1+passo_x , posy+passo_y)
    linha(posx + lado_1, posy + lado_2, posx+lado_1+passo_x , posy+lado_2+passo_y)
    
    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.
def linha_b(p_1, p_2):
    """ Desenha uma linha entre dois pontos."""
    # posiciona-se
    turtle.pu()
    turtle.goto(p_1[0],p_1[1])
    turtle.pd()
    # Desenha
    turtle.goto(p_2[0],p_2[1])
    
def bloco_b(posx, posy,lado_1, lado_2, angulo):
    """ Desenha um bloco geométrico."""
    # Auxiliares
    passo_x = lado_2 * math.cos(angulo)
    passo_y = lado_2 * math.sin(angulo)
    # Pontos
    p_1 = (posx, posy)
    p_2 = (posx + lado_1, posy)
    p_3 = (posx + lado_1 + passo_x,posy + passo_y)
    p_4 = (posx + passo_x, posy+passo_y)
    p_5 = (posx, posy+lado_2)
    p_6 = (posx + lado_1, posy + lado_2)
    p_7 = (posx + lado_1 + passo_x, posy + lado_2 + passo_y)
    p_8 = (posx + passo_x, posy + lado_2 + passo_y)
    
    # Desenho
    linha_b(p_1, p_2)
    linha_b(p_2, p_3)
    linha_b(p_3, p_4)
    linha_b(p_4, p_1)
    
  
    linha_b(p_5, p_6)
    linha_b(p_6, p_7)
    linha_b(p_7, p_8)
    linha_b(p_8, p_5)
    
    linha_b(p_1, p_5)
    linha_b(p_2, p_6)
    linha_b(p_4, p_8)
    linha_b(p_3, p_7)
    
    
    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.
def rectangulo(posx, posy, lado_1, lado_2):
    """ Desenha um rectangulo com o canto inferior esquerdo em (posx, posy)."""
    # Posiciona-se
    turtle.pu()
    turtle.goto(posx,posy)
    turtle.pd()
    # desenha
    for i in range(2):
        turtle.fd(lado_1)
        turtle.left(90)
        turtle.fd(lado_2)
        turtle.left(90)
    turtle.hideturtle()
    
def linha(x_1,y_1, x_2,y_2):
    """ Desenha uma linha entre dois pontos."""
    # posiciona-se
    turtle.pu()
    turtle.goto(x_1,y_1)
    turtle.pd()
    # Desenha
    turtle.goto(x_2,y_2)
Agora, de novo, a questão que resta resolver é a gestão dos pontos.
def main_2(posx, posy, lado_1, lado_2):
    """ Desenha o bloco."""
    # auxiliares
    canto_2_x = lado_2 * math.cos(math.pi/6)
    canto_2_y = lado_2 * math.sin(math.pi/6)
    # rectangulos
    rectangulo(posx, posy, lado_1, lado_2)
    rectangulo(canto_2_x, canto_2_y, lado_1, lado_2)
    # linhas
    linha(posx, posy, canto_2_x,canto_2_y)
    linha(posx, posy+lado_2, canto_2_x,canto_2_y + lado_2)
    linha(posx + lado_1, posy, canto_2_x + lado_1,canto_2_y)
    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.
def quase_losango(posx, posy, lado_1, lado_2, angulo, orientacao):
    # Posiciona-se
    turtle.pu()
    turtle.goto(posx,posy)
    turtle.pd()
    turtle.setheading(orientacao)
    # desenha 
    for i in range(2):
        turtle.fd(lado_1)
        turtle.left(angulo)
    
        turtle.fd(lado_2)
        turtle.left(180-angulo)
    turtle.hideturtle()
Isto resolvido, vamos ter o desenho de quatro quase-losangos (na realidade dois são mesmo losangos...).

def main_3(posx, posy, lado_1, lado_2):
    """ Desenha o bloco."""
    # auxiliares
    canto_2_x = lado_2 * math.cos(math.pi/6)
    canto_2_y = lado_2 * math.sin(math.pi/6)
    # quase losangos :pela ordem maior-frente, menor-esquerdo, menor-direito, maior-trás
    quase_losango(posx, posy, lado_1, lado_2, 30,0)
    quase_losango(posx, posy, lado_2, lado_2, 60,30)
    quase_losango(posx+lado_1, posy, lado_2, lado_2, 60,30)
    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