Programar é uma actividade complexa. Obriga-nos a dominar o domínio do problema (se eu não souber o que são números primos como posso escrever um programa para verificar se um número é primo?), obriga-nos a conhecer uma linguagem de programação (se eu precisar de ler informação guardada num ficheiro externo e não conhecer as instruções sobre ficheiros, como posso esperar resolver o problema?). Mas mesmo tendo estas duas competências, ainda é preciso outra, fundamental: como passar do problema ao programa, como
dominar a complexidade inerente ao desenvolvimento de um programa? Vamos ver um dos modos de dominar essa complexidade, que consiste em decompor um problema em sub-problemas mais simples, algo que Descartes enunciou de modo claro.
Vamos supor que o nosso problema consiste em querer desenhar numa tela um caminho aleatório formado por segmentos de recta. Vamos supor ainda que nos pedem que fique guardado num ficheiro externo uma representação do caminho. Vamos então proceder por partes.
01.
def
caminho_alea(n):
02.
03.
04.
05.
06.
pass
07.
08.
if
__name__
=
=
'__main__'
:
09.
caminho_alea(
30
)
O que nos diz esta solução? Bom , diz-nos que decidimos ter
n segmentos, que vão ser gerados, depois guardados, a seguir lidos e finalmente desenhados. Ou seja: dividimos o nosso problema inicial em quatro sub-problemas. O curioso da questão é que este programa corre sem erros, embora não faça nada…
Como vamos resolver cada sub-problema? Por que ordem? Vamos começar por resolver o primeiro dos quatro. Vamos tornar mais específico o enunciado. Vamos admitir que um segmento é representado pelos seus pontos extremos. Vamos ainda concretizar os valores possíveis para dada um dos pontos. Como se trata de um caminho, vamos garantir a ligação entre os segmentos impondo que os segmentos consecutivos têm uma extremidade em comum. Isso tem como consequência que eu possa pensar em termos de pontos consecutivos e não em segmentos. Assim preciso de um programa que gere pontos, podendo cada um deles assumir valores num dado intervalo. Os pontos serão representados por uma
lista com as coordenadas do ponto.
01.
import
random
02.
03.
def
gera_pontos(n, inf, sup):
04.
pontos
=
[]
05.
for
i
in
range(n):
06.
pontos.append([random.randint(inf,sup), random.randint(inf,sup)])
07.
return
pontos
08.
09.
def
gera_pontos_2(n,inf,sup):
10.
return
[[random.randint(inf,sup), random.randint(inf,sup)]
for
i
in
range(n)]
Como se pode ver, apresentamos duas soluções alternativas, uma primeira convencional e, uma segunda, que recorre a
listas por compreensão. Em qualquer das soluções usamos listas como contentores onde são guardados os pontos. Os valores são gerados aleatoriamente recorrendo ao módulo random. Com este sub-problema resolvido, e que pode ser testado isoladamente, podemos avançar no nosso programa.
1.
def
caminho_alea(n, inf,sup):
2.
3.
pontos
=
gera_pontos(n, inf, sup)
4.
5.
6.
Note-se que o número de parâmetros foi alterado e também o significado do primeiro. Agora não traduz o número de segmentos mas antes o número de pontos. Continuemos. Vamos guardar os pontos num ficheiro externo,
um ponto por linha.
1.
def
guarda_caminho(ficheiro, dados):
2.
fich_dados
=
open(ficheiro,
'w'
)
3.
for
x,y
in
dados:
4.
linha
=
str(x)
+
'\t'
+
str(y)
+
'\n'
5.
fich_dados.write(linha)
6.
fich_dados.close()
Esta solução não tem nada de especial: abrimos um ficheiro para escrita (criando-o caso não exista), e depois vamos percorrer a lista de pontos retirando-os um a um, fabricando a cadeia de caracteres que representa a linha e escrevendo essa linha no ficheiro. Notar que existe uma tabulação entre cada coordenada e que a linha termina com a
marca de fim de linha. Este programa pode ser testado isoladamente! Por outro lado, sendo autónomo podemos usá-lo noutro tipo de aplicação. A modularidade compensa!! E de novo a nossa solução global pode ser refinada.
1.
def
caminho_alea(n, inf,sup, ficheiro):
2.
3.
pontos
=
gera_pontos(n, inf, sup)
4.
5.
guarda_caminho(ficheiro,pontos)
6.
7.
Notar o aparecimento de mais um parâmetro, o nome do ficheiro. Resolvida esta questão vamos passar à operação inversa: obter as coordenadas a partir do ficheiro.
01.
def
ler_caminho(ficheiro):
02.
fich_dados
=
open(ficheiro,
'r'
)
03.
valores
=
[]
04.
for
linha
in
fich_dados:
05.
ponto
=
linha[:
-
1
].split(
'\t'
)
06.
x
=
int(ponto[
0
])
07.
y
=
int(ponto[
1
])
08.
valores.append([x,y])
09.
fich_dados.close()
10.
return
valores
O que se pode dizer sobre esta solução? Talvez chamar a atenção para o modo como se transformou uma cadeia de caracteres que representa uma linha do ficheiro, numa lista com dois inteiros.
linha[:-1] retira a marca de fim de linha.
split(’\t’) divide o que resta numa lista com duas cadeias de caracteres que representam as coordenadas. Como são
cadeias de caracteres foi preciso converter cada uma num inteiro. E nova solução global.
1.
def
caminho_alea(n, inf,sup, ficheiro):
2.
3.
pontos
=
gera_pontos(n, inf, sup)
4.
5.
guarda_caminho(ficheiro,pontos)
6.
7.
pontos
=
ler_caminho(ficheiro)
8.
Já só nos falta mostrar o caminho. Vamos fazê-lo recorrendo ao módulo
matplotlib.
01.
import
matplotlib.pyplot as plt
02.
03.
def
mostra_caminho(pontos):
04.
plt.title(
'Caminho'
)
05.
plt.xlabel(
'X'
)
06.
plt.ylabel(
'Y'
)
07.
lista_x
=
[ x
for
x,y
in
pontos]
08.
lista_y
=
[y
for
x,y
in
pontos]
09.
plt.plot(lista_x,lista_y)
10.
plt.show()
Nesta solução, que uma vez mais podemos testar isoladamente, a única dificuldade residia no facto de termos que separar os pontos em duas listas, cada uma com os valores de cada coordenada. Para os que gostam de aventuras mais arrojadas, deixamos aqui uma alternativa à conversão dos dados que recorre ao iterador
zip.
01.
import
matplotlib.pyplot as plt
02.
03.
def
mostra_caminho(pontos):
04.
plt.title(
'Caminho'
)
05.
plt.xlabel(
'X'
)
06.
plt.ylabel(
'Y'
)
07.
coord
=
zip(
*
pontos)
08.
plt.plot(
*
coord)
09.
plt.show()
Um dia explicaremos o porquê do asterisco... E agora vamos juntar tudo.
01.
def
caminho_alea(n, inf,sup, ficheiro):
02.
03.
pontos
=
gera_pontos(n, inf, sup)
04.
05.
guarda_caminho(ficheiro,pontos)
06.
07.
pontos
=
ler_caminho(ficheiro)
08.
09.
mostra_caminho(pontos)
E pronto. Já está. Ou talvez não… Olhando para a solução acima é um pouco bizarro que tendo os pontos logo após o primeiro passo, vamos estar a lê-los a partir do ficheiro. Daí se possa simplificar o programa.
1.
def
caminho_alea(n, inf,sup, ficheiro):
2.
3.
pontos
=
gera_pontos(n, inf, sup)
4.
5.
guarda_caminho(ficheiro,pontos)
6.
7.
mostra_caminho(pontos)
Agora sim: geramos, guardamos e mostramos!
Moral da estória: dividir compensa!