domingo, 13 de dezembro de 2015

Ficheiros (II)

Vamos tentar resolver o problema de eliminar uma linha de um ficheiro. O ficheiro vai ter que ser lido e temos que identificar a linha a eliminar. Faz sentido fazer então uma leitura por linhas. Vejamos uma solução simples.
def elimina_linha_1(ficheiro,n):
    """Elimina a linha n do ficheiro."""
    with open(ficheiro,'r',encoding='utf-8') as fich:
        # lê as linhas
        linhas = fich.readlines()
        fich.close()
    with open(ficheiro,'w',encoding='utf-8') as fich:
        # elimina a linha n
        linhas.pop(n)
        # escreve nova versão
        fich.writelines(linhas)
        fich.close()
Como fizemos??? Lemos e guardámos todas as linhas. Depois eliminámos a linha pretendida. Finalmente apagámos o ficheiro antigo com a abertura em modo ‘w’ e colocámos lá a nova informação. Claro que podíamos ter feito uma leitura inicial linha a linha filtrando a linha pretendida. O resultado seria:
def elimina_linha_2(ficheiro,n):
    """Elimina a linha n do ficheiro."""
    with open(ficheiro,'r',encoding='utf-8') as fich:
        linhas = []
        for i,linha in enumerate(fich):
            if i != n:
                linhas.append(linha)
        fich.close()
    with open(ficheiro,'w',encoding='utf-8') as fich:
        fich.writelines(linhas)
        fich.close() 
Admita que agora a especificação é ligeiramente diferente. Não se trata de eliminar mas de substituir. O que fizemos anteriormente pode servir-nos de inspiração:
def substitui_linha_1(ficheiro,n, nova_linha):
    """Substitui linha n do ficheiro."""
    with open(ficheiro,'r',encoding='utf-8') as fich:
        novas_linhas = []
        for i,linha in enumerate(fich):
            if i == n:
                novas_linhas.append(nova_linha)
            else:
                novas_linhas.append(linha)
    with open(ficheiro,'w',encoding='utf-8') as fich:
        fich.writelines(novas_linhas)
Nesta solução, vamos copiando as linhas a manter e colocando a nova no lugar pretendido. Nada de especial, certo? Às vezes mais vale tentar uma solução simples mas que resulta do que uma rebuscada que nos pode trazer alguns dissabores. Mas nada impede agora que tente as suas soluções para as duas questões apresentadas.

Ficheiros (I)

Suponha que anda à procura num ficheiro de texto de um dado padrão. Como resolver o problema? Se pensar um pouco chegará à conclusão que vai ter que ler o ficheiro e analisar a cadeia de caracteres para determinar se o padrão o corre ou não. Podemos ler o ficheiro todo de uma vez, linha a linha ou todas as linhas (neste caso na forma de uma lista de linhas). Vejamos as diferentes soluções:
def wally_0(ficheiro,padrao):
    """wally está no ficheiro?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        texto = f_ent.read()
        if padrao in texto:
            return True
        return False



def wally_1(ficheiro,padrao):
    """wally está no ficheiro?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        for linha in f_ent:
            if padrao in linha:
                return True
        return False

    
def wally_11(ficheiro,padrao):
    """wally está no ficheiro?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        texto = f_ent.readlines()
        for linha in texto:
            if padrao in linha:
                return True
        return False 

Suponhamos agora que a pergunta muda ligeiramente, e nos pedem para dizer não apenas se o padrão existe, mas também em que linha existe. Uma vez mais, uma breve reflexão leva-nos a concluir que ler todo o ficheiro de uma só vez não é agora adequado. Também ler todas as linhas não nos parece a melhor ideia: afinal mal encontremos o padrão podemos abandonar o programa e não precisamos de guardar na memória todos as linhas do ficheiro, que podem ser em número muito elevado. Como fazer? Essa uma resposta.
def wally_2(ficheiro,padrao):
    """em que linha está wally  no ficheiro?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        for i,linha in enumerate(f_ent):
            if padrao in linha:
                return i+1
        return -1 
como se observa no código acima, usamos a função enumerate para nos resolver a dificuldade de ler as linhas e saber o seu número. E se agora nos disserem que se pretendem as linhas de todas as ocorrências? Hum,…, para isso precisamos de um contentor onde guardar a informação que vamos recolhendo por análise do ficheiro linha a linha.
def wally_3(ficheiro,padrao):
    """em que linhas está wally  no ficheiro?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        num_lin = []
        for i,linha in enumerate(f_ent):
            if padrao in linha:
                num_lin.append(i+1)
        return num_lin 
Não param de mudar o que nos pedem: passou a ser a linha e a posição dentro da linha. Para resolver lá nos temos que socorrer dos métodos conhecidos sobre cadeias de caracteres:
def wally_4(ficheiro,padrao):
    """em que linha e posição na linha está wally?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        for i,linha in enumerate(f_ent):
            pos = linha.find(padrao)
            if pos != -1:
                return i+1,pos
        return -1,-1
Como se pode ver o método find foi a nossa salvação. E este problema ainda mas para todas as ocorrências?
def wally_5(ficheiro,padrao):
    """em que linhas e posições nas linhas está wally?"""
    with open(ficheiro,'r',encoding='utf-8') as f_ent:
        num_pos_lin = []
        for i,linha in enumerate(f_ent):
            pos = linha.find(padrao)
            if pos != -1:
                num_pos_lin.append((i+1,pos))
        return num_pos_lin 
E se …. A sua imaginação é o limite. Tente perceber as alternativas e o modo como ficheiros, cadeias de caracteres e listas estão ligados entre si.

Os modos dos ficheiros...

Vimos nas aulas que os ficheiros podem ter vários modos básicos de abertura: leitura (r) , escrita (w) e acrescentar (a). Estes modos podem ser combinados em leitura e escrita adicionando o sufixo ‘+’. Na tabela abaixo procuramos mostrar o significado dos seis modos.
Há quem diga que uma imagem vale mais do que mil palavras (ou tabelas…), pelo que mostramos a mesma informação de outra maneira.
Deve ter em atenção que no modo ‘w+’ pode ler e escrever, mas só pode ler depois de ter escrito algo pois o ficheiro inicialmente tem todo o conteúdo apagado.

domingo, 29 de novembro de 2015

Vermes em passeio

Vamos regressar ao módulo turtle para ilustrar um aspecto pouco referido nas aulas. É possível a tartaruga marcar os pontos por onde passou graças ao comando stamp(). Também se pode apagar as marcas com vários comandos, por exemplo, com clearstamps(). O leitor é convidado a consultar o manual da linguagem para os detalhes. O exemplo que se segue mostra como podemos simular um passeio (mais ou menos) aleatório de um verme.
import turtle
import random

def bug(length, step, life):
    # initialization
    turtle.penup()
    theta = 0
    dtheta = 1
    turtle.color(random.choice([‘red','blue','yellow','green','black']))
    # draw bug
    for j in range(length):
        turtle.forward(step)
        turtle.left(theta)
        theta += dtheta
        turtle.stamp()  
    # move
    for i in range(life):
        turtle.clearstamps(1)
        if abs(ycor()) > 400:
            turtle.left(30)
        if abs(xcor()) > 400:
            turtle.right(30)
        if theta > 10 or theta < -10:
            dtheta = -dtheta                
        turtle.forward(step)
        turtle.left(theta)
        theta += dtheta
        turtle.stamp()

if __name__ == '__main__':
    turtle.setworldcoordinates(-500, -500, 500, 500)
    bug(10, 15, 500)
    turtle.exitonclick()
O programa acima tem três parâmetros: o comprimento do verme (length), a amplitude de cada movimento (step), e o numero de movimentos (life), Tem também três partes distintas. Na primeira, inicializamos o sistema, definimos o valor da variação da orientação que nos permite simular um movimento ondulatório (dtheta), e escolhemos aleatoriamente uma cor. Na segunda, o primeiro ciclo for, desenhamos o verme inicial. Na terceira, pomos o verme e movimentar-se. Notar-se-á que fechamos a zona onde se pode movimentar, um quadrado 400X400. Com o método setworlcoordinates() limitamos o tamanho máximo do mundo. Execute o programa para ter uma ideia do que se passa. Procure alterar os vários parâmetros e ver as consequências. Altere o programa para que o passeio seja mais naturalmente aleatório. Nota: Este exemplo foi adaptado do livro de Mark J. Johnson "A concise introduction to programming ion Python".

Árvore Genealógica

No texto de apoio à disciplina é introduzido um conceito simplista de árvore genealógica. Estas árvores são representadas por um dicionário em que as chaves são nomes de pessoas (um progenitor) e os valores são listas de nomes de pessoas (os filhos). Esta representação permite, por exemplo, obter os filhos de alguém de modo trivial:
def filhos(dicio,progenitor):
    """ lista dos filhos."""
    return dicio.get(progenitor,[])
Também é fácil listar os netos de alguém. Por exemplo:
def netos(dicio,progenitor):
    """ Lista netos. Filhos dos filhos"""
    desc1 = filhos(dicio,progenitor)
    if desc1:
        net = []
        for elem in desc1:
            desc2 = filhos(dicio,elem)
            if desc2:
                net = net + desc2
        return net
    else:
        return []
Esta solução calcula primeiro os filhos e, caso existam, os filhos dos filhos. Não é difícil propor versões alternativas para esta questão. Por exemplo:
def netos_b(arv_genea,progenitor):
    net = []
    desc1 = filhos(arv_genea,progenitor)
    for prog in desc1:
        net.extend(filhos(arv_genea,prog))
    return net
Aqui simplificamos o código no interior do ciclo recorrendo ao método extend. Mas ainda podemos aproximarmo-nos mais da defino natural: netos são os filhos dos filhos:
def netos_b(dicio ,progenitor):
    """ Lista dos netos. Os filhos dos filhos"""
    return filhos_b(dicio,filhos_b(dicio,[progenitor]))

def filhos_b(dicio,lista_progenitores):
    lista_filhos = []
    for filho in lista_progenitores:
        lista_filhos.extend(dicio.get(filho,[]))
    return lista_filhos
Note-se que tivemos que alterar a função filhos para agora me dar a lista dos filhos de um conjunto de pessoas.
Agora o conceito básico de progenitor de alguém:
def progenitor(ag,nome):
    for p,fil in ag.items():
        if nome in fil:
            return p
    return None
Neste caso vamos procurar o nome na lista dos filhos de alguém que, a existir, será o progenitor. Passemos a outros exemplos. Irmãos são pessoas que têm um progenitor comum. Com está organizada a árvore genealógica cada pessoa tem um progenitor único, logo:
def irmaos(ag,nome1,nome2):
    """ Têm o mesmo progenitor?"""
    prog1 = progenitor(ag, nome1)
    prog2 = progenitor (ag,nome2)
    return prog1 == prog2
Socorrendo-nos de novo da ideia de progenitor podemos definir o conceito de avô/avó.
def avo(dic,nome):
    """ Quem é o avô/avó do nome."""
    prog = progenitor(dic,nome)
    if prog:
        return progenitor(dic,prog)
    return None
Deixamos ao leitor o cuidado de definir outras relações de parentesco. Por exemplo, tente o conceito de primos (os filhos de irmãos). Por outro lado, alguns dos conceitos acima são booleanos. Imagine que em vez de se saber se duas pessoas são primos se pretende saber quais os primos de alguém. Não lhe deve ser difícil chegar à solução.
Finalmente, pense numa implementação mais realista de uma árvore genealógica, em que cada pessoa tem associado os seus pais. E faça tudo de novo!!!

domingo, 22 de novembro de 2015

Listas por Compreensão (II)

Estamos habituados em matemática a definir um conjunto de duas formas distintas: por extensão, quando indicamos os seus elementos, ou por intenção, quando indicamos uma regra que nos permite identificar os elementos do conjunto. Em Python podemos descrever uma lista de elementos também destas duas formas. À segunda chamamos listas por compreensão. As listas por compreensão estão ligadas à resolução de problemas que obedecem a um certo padrão. Por exemplo, admitamos que queremos um programa que gere números inteiros, aleatoriamente, entre um certo intervalo. Uma solução simples seria:
import random

def gera_numeros(n,inf, sup):
     res = []
     for i in range(n):
          num = random.randint(inf,sup)
          res.append(num)
     return res
Este padrão em que temos um acumulador onde vão sendo guardados elementos através de um processo repetitivo, pode ser implementado também por recurso a listas por compreensão:
def gera_numeros_b(n,inf, sup):
     res = [ random.randint(inf,sup) for i in range(n)]
     return res
Como se percebe a sintaxe envolve, nesta versão básica, os indicadores de lista (“[“ e “]”), seguido de uma expressão que nos permite gerar os elementos da lista, seguido do processo repetitivo (ciclo for). Podemos aplicar este modelo em diferentes situações.

Elevar os números de aula lista ao quadrado:
def quadrados(lista):
     return [elem**2 for elem in lista]
Produto escalar de dois vectores:
def escalar_comp(x,y):
     """ Produto escalar de dois vectores."""
     return sum([x[i]*y[i] for i in range(len(x))])
A lista por compreensão gera os produtos e a função sum faz a soma.
Somas parciais (percorrer a lista e gerar uma nova list em que na posição i é colocada a soma dos números da primeira lista desde o início até (inclusive) i):
def somas_parciais(lista):
     """ somas de 1 a i."""
     return [sum(lista[:i+1]) for i in range(len(lista))]
Podemos dizer que, de um modo geral ,este padrão obedece ao modelo:
def my_map(func, lista):
     return [func(elem) for elem in lista]
O leitor poderá pensar que não é muito poderoso este modo de programar. Por exemplo, não podemos filtrar elementos a incluir na lista em função de um dado critério. Mas as listas por compreensão têm uma sintaxe mais completa. Admitamos que queremos construir uma lista a partir de outra, retendo apenas os seus elementos positivos:
def filtro_nega(lista):
     return [ elem for elem in lista if elem > 0]
  
Agora temos a expressão, seguida do ciclo for, seguida do if. Outros exemplos: Lista dos índices das ocorrências de um dado elemento:
def ocorre(elem, lista):
     """ localizaçoes das ocorrências de elem na lista."""
     return [ i for i in range(len(lista)) if lista[i] == elem]
Conta o número de ocorrências:
def conta(elem, lista):
     """Quantas ocorrências de elem na lista."""
     return sum([ 1 for e in lista if e == elem])
Parece melhor. Mas e problemas em que existem mais do que um ciclo? Por exemplo, quando pretendemos construir a lista de pares ordenados formados com elementos de outras duas listas fazemos:
def combina(a,b):
     """ pares ordenados com elementos de a e de b."""
     res = []
     for elem_a in a:
          for elem_b in b:
               res.append((elem_a,elem_b))
     return res
Mas também esta situação está contemplada:
def combina_b(a,b):
     return [(elem_a,elem_b) for elem_a in a for elem_b in b]
Como se vê a sintaxe é dada pela expressão, seguida do ciclo mais externo, seguida do ciclo mais interno. No ciclo interno podemos referir objetos do ciclo externo.

Um exemplo simples consiste em obter a lista dos elementos de uma lista de listas:
def aplana(lista_listas):
     """Aplanar uma lista de listaas."""
     return [elem for linha in lista_listas for elem in linha  ]
Podemos ainda complicar mais as coisas? Podemos! Numa lista por compreensão no lugar da expressão podemos ter … uma lista por compreensão!!! Vejamos como esse facto nos permite obter a transposta de uma matriz de modo simples:
def transposta_b(matriz):
     """transposta de uma matriz."""
     return [ [matriz[j][i] for j in range(len(matriz))] for i in range(len(matriz[0]))]
Agora a expressão é uma lista por compreensão e deve ser considerada como estando no interior do ciclo for que aparece a seguir! Podemos usar este código para escrever um programa que roda 90 graus no sentido dos ponteiros do relógio uma imagem a preto e branco representada por uma lista de listas de 1s e 0s.
def roda_90(imagem):
     """ Rodar 90 graus uma imagem."""
     img_trans = transposta_b(matriz)
     nova_imagem = [linha[::-1]  for linha in img_trans]
     return nova_imagem
Estaremos limitados a dois ciclos? Mais uma vez a resposta é negativa. Segue-se um exemplo, que mostra como se podem obter três inteiros que verificam a condição do Teorema de Pitágoras:
def pitagoras(n):
     return [(x,y,z) for x in range(1,n) for y in range(1,n) for z in range(1,n) if x**2 + y**2 == z**2]
Executando o programa verificamos a ocorrência de repetições. Mas podemos resolver facilmente esse problema:
def pitagoras_b(n):
     return [(x,y,z) for x in range(1,n) for y in range(x,n) for z in range(y,n) if x**2 + y**2 == z**2]
E se em vez da potência ser 2 fosse outro inteiro maior do que 2? Estamos perante o chamado último Teorema de Fermat. Mas não aconselho a tentar prová-lo deste modo…
def fermat(n,k):
     return [(x,y,z) for x in range(1,n) for y in range(x,n) for z in range(y,n) if x**k + y**k == z**k]
O leitor continua a pensar que tudo isto é muito limitado. Vamos então a um exemplo em que a expressão é um pouco mais complexa, pois inclui uma condicional. Neste caso podemos implementar uma função em que todos os elementos de uma lista de determinado valor são substituídos por outro elemento:
def my_replace(novo, velho,lista):
     return [novo if elem == velho else elem for elem in lista]
Pois é, estamos perante um novo tipo de expressão: objecto if condição else objecto. Vamos usar esta ideia para obter o negativo de uma imagem a preto e branco:
def negativo_img(imagem):
     return [[ 0 if elem == 1 else 1  for elem in linha] for linha in imagem]
Para terminar, vamos deixá-lo com um exemplo que nos permite calcular uma lista de números primos, recorrendo ao algoritmo conhecido por Sieve of Erathostenes.
def primos(n):
     up = int(math.sqrt(n))
     no_primes = [j for i in range(2,up+1) for j in range(i**2,n+1,i)]
     return [ x for x in range(2,n+1) if x not in no_primes]
Interessante, não concorda? Talvez. Chegados aqui coloca-se a questão de saber se é possível realizar com listas por compreensão coisas que não se podem fazer de outro modo. E resposta é um redondo não. Então porquê usar? Se olharmos para os exemplos anteriores é claro que há medida que queremos fazer coisas mais complexas a legibilidade do programa diminui. No entanto existe uma razão forte: o uso de listas por compreensão torna os programas muito mais rápidos! Então o nosso conselho final é: use as listas por compreensão sempre que o tempo de execução for crítico ou, não sendo crítico, sempre que a legibilidade do programa não seja comprometida.

sexta-feira, 20 de novembro de 2015

Desenhar uma grelha: um exercício de programação

Nas aulas discutimos o problema de desenhar uma grelha rectangular com n células, cada uma um quadrado com um dado comprimento do lado. Uma das soluções que apareceu baseava-se na ideia de desenhar a grelha como nós humanos geralmente fazemos: desenhar separadamente as linhas verticais e as horizontais. Vejamos uma solução básica:
import turtle

def grelha_1(dim, lado):
    """ Solução básica."""
    # verticais
    turtle.setheading(90)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(i * lado,0)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)
    # horizontais
    turtle.setheading(0)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(0,i*lado)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)    
    turtle.hideturtle()
Como se pode ver as linhas são desenhadas em separado e cada tipo de linha (verticais ou horizontais) é desenhada no interior de um ciclo. A parte relevante em cada ciclo é o posicionamento da tartaruga para desenhar a linha. Fazemos isso através de um goto.
Uma primeira alteração possível é considerar a possibilidade de controlar a localização do canto inferior esquerdo. Vejamos como se pode fazer.
def grelha_2(dim, lado,pos_x,pos_y):
    """ Controlando a posição do canto inferior esquerdo."""
    # verticais
    turtle.setheading(90)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x + i * lado,pos_y)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)
    # horizontais
    turtle.setheading(0)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x,pos_y+i*lado)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)    
    turtle.hideturtle()
Como se notará a alteração é mínima, e traduz-se a colocar o valor das coordenadas do canto inferior esquerdo no sítio certo.
Suponhamos agora que nos pedem uma solução em que seja também possível controlar a orientação do quadrado. Esta questão já obriga a uma ginástica adicional, mas a questão central mantém-se a mesma: definir os pontos em que se iniciam as linhas. Eis uma solução, não muito elegante, mas que funciona…
def grelha_4(dim, lado,pos_x,pos_y,orient):
    """ Controlando a posição e a orientação. """
    # verticais
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x,pos_y)
        turtle.setheading(orient)
        turtle.forward(i*lado)
        turtle.setheading(90+orient)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)
    # horizontais
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x,pos_y)
        turtle.setheading(90+orient)
        turtle.forward(i*lado)
        turtle.setheading(orient)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)    
    turtle.hideturtle()
Outras alterações poderiam ser feitas, como seja mudar a espessura das linhas ou a sua cor. Mas o que não nos agrada é a legibilidade do código. Afinal o nosso ponto de partida foi considerar o desenho da grelha com base no conceito de linha, mas esse conceito está explicitamente ausente nas soluções acima. Vamos remediar a situação definindo uma função que nos permite desenhar uma linha, com uma dada posição inicial uma orientação e um dado comprimento. Não é difícil.
def linha(pos_x,pos_y, orient, tam):
    # posiciona
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orient)
    turtle.pendown()
    # desenha
    turtle.forward(tam)
    turtle.hideturtle()
Na posse desta nova construção (abstracção), podemos refazer as soluções acima apresentadas. Comecemos pela básica:
def grelha_5(dim, lado):
    """ Solução básica."""
    # horizontais
    for i in range(dim+1):
        linha(0,i*lado,0,dim*lado)
    # verticais
    for i in range(dim+1):
        linha(i*lado,0,90,dim*lado)
    turtle.hideturtle()
O leitor concordará que corresponde de modo mais claro à forma como enunciámos a solução. Passemos à posição.
def grelha_6(dim, lado,pos_x,pos_y):
    """ Solução com controlo da posição do canto inferior esquerdo."""
    # horizontais
    for i in range(dim+1):
        linha(pos_x,pos_y+i*lado,0,dim*lado)
    # verticais
    for i in range(dim+1):
        linha(pos_x+i*lado,pos_y,90,dim*lado)
    turtle.hideturtle()
Elementar, não acha? Finalmente a orientação. Aqui decidimos usar um pouco dos nossos conhecimentos de trignometria, para definir as novas posições de início das linhas.
Na posse deste conhecimento a solução vem, finalmente, como:
import math

def grelha_7(dim, lado,pos_x,pos_y,orient):
    """ Solução com controlo da posição do canto inferior esquerdo e a orientação."""
    deg_rad = math.pi/180
    # horizontais
    for i in range(dim+1):
        linha(pos_x+i*lado*math.cos((orient+90)* deg_rad),pos_y+ i*lado*math.sin((orient+90)* deg_rad),orient,dim*lado)
    # verticais
    for i in range(dim+1):
        linha(pos_x+i*lado*math.cos(orient * deg_rad),pos_y+ i*lado*math.sin(orient * deg_rad),90+orient,dim*lado)
    turtle.hideturtle()
Chegados a este ponto podemos achar que o trabalho está feito e, por isso, podemos passar a outro problema. Mas… e se alguém nos pedir a nossa solução para criar um tabuleiro de xadrez? Precisamos colorir as células, mas como fazer? A dificuldade reside no facto de termos olhado para a grelha não como uma grelha, isto é formada por células justapostas, mas como linhas que se cruzam. E precisamos partir de novo à aventura: criar a dita grelha formada por quadrados. Mas aprendemos algo com o caso anterior, a saber: usar abstração para criar primitivas é positivo.
Para começar precisamos de uma primitiva para desenhar um quadrado:
def quadrado(pos_x, pos_y, lado, orient):
    # posiciona
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orient)
    turtle.pendown()
    # desenha
    for i in range(4):
        turtle.forward(lado)
        turtle.lt(90)
    turtle.hideturtle()
E vamos percorrer de novo a nossa via sacra. Primeiro a versão básica:
def grelha_8(dim, lado):
    """ Solução básica."""
    # Por linhas
    for i in range(dim):
        # linha i
        for j in range(dim):
            quadrado(j*lado,i*lado,lado,0)    
    turtle.hideturtle()
Não muito diferente, certo? Só que agora temos uma perspectiva matricial, pelo que precisamos de um ciclo dentro de outro ciclo. Controlar a posição é trivial:
def grelha_9(dim, lado, pos_x, pos_y):
    """ Solução com controlo da posição do canto inferior esquerdo."""
    # Por linhas
    for i in range(dim):
        # linha i
        for j in range(dim):
            quadrado(pos_x+j*lado,pos_y+i*lado,lado,0)    
    turtle.hideturtle()
A quatro da orientação pede um pouco mais de atenção como na versão anterior, mas a lógica é semelhante: trata-se de definir os pontos iniciais para cada quadrado:
def grelha_10(dim, lado, pos_x, pos_y, orient):
    """ Solução com controlo da posição do canto inferior esquerdo e da orientação."""
    deg_rad = math.pi/180
    # Por linhas
    for i in range(dim):
        # linha i
        p_x = pos_x+i*lado*math.cos((orient+90)*deg_rad)
        p_y = pos_y+i*lado*math.sin((orient+90)*deg_rad)        
        for j in range(dim):
            quadrado(p_x,p_y,lado,orient)
            p_x = p_x+lado*math.cos(orient*deg_rad)
            p_y = p_y+lado * math.sin(orient*deg_rad)                   
    turtle.hideturtle()
Notar que nesta solução o ciclo interior apenas controla o número de vezes que desenhamos um quadrado numa linha.

. Agora sim podemos dar o trabalho por encerrado!!! Mas, espere aí, ouço-o dizer, a passagem para o ponto de vista das células quadradas não era para termos mais graus de liberdade, nomeadamente em relação à cor dos quadrados??? É verdade sim senhor. Então vamos a isso. A solução mais simples consistirá em poder desenhar quadrados coloridos…
def quadrado_cor(pos_x, pos_y, lado, orient,cor):
    # cor
    turtle.color(cor)
    # posiciona
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orient)
    turtle.pendown()
    # desenha
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.lt(90)
    turtle.end_fill()
    turtle.hideturtle()
E agora eis o nosso tabuleiro bi-color:
def grelha_11(dim, lado, pos_x, pos_y, orient):
    """ Solução com controlo da posição do canto inferior esquerdo e da orientação."""
    deg_rad = math.pi/180
    # Por linhas
    for i in range(dim):
        # linha i
        p_x = pos_x+i*lado*math.cos((orient+90)*deg_rad)
        p_y = pos_y+i*lado*math.sin((orient+90)*deg_rad)        
        for j in range(dim):
            if (i+j)%2 == 0:
                quadrado_cor(p_x,p_y,lado,orient,'black')
            else:
                quadrado_cor(p_x,p_y,lado,orient,'gray')
            p_x = p_x+lado*math.cos(orient*deg_rad)
            p_y = p_y+lado * math.sin(orient*deg_rad)                   
    turtle.hideturtle() 
Veja apenas como conseguimos o efeito das cores alternadas… Experimente o código e aprecie o resultado.
Se quiser outro tipo de tabuleiros coloridos é só adaptar. Agora é mesmo a sua vez de fazer alguma coisa. Eu vou descansar um pouco!