
A programação é um campo onde se juntam os quatro aspectos referidos: ciência e arte, engenharia e design. O que os une: a criatividade! Um excelente programa é aquele que é eficaz e eficiente, mas também belo, elegante e funcional. Esses aspectos sobressaem tanto mais quanto mais complexo for o problema que são supostos resolver. Dominar a complexidade é pois a tarefa principal do programador consciente. Isso só se consegue com rigor e disciplina, mas também com treino, exploração e intuição.
O exemplo que a seguir apresentamos é simples, servindo apenas para demonstrar algumas ideias básicas sobre como dominar a complexidade. O problema é o seguinte: quais os pares de números inteiros positivos, até um certo limite, que são primos entre si. Dos números dizem-se primos entre si se apenas admitirem como divisores comuns a unidade.
A estratégia será a seguinte:
a) gerar os pares de números até ao limite dado
b) para cada par: calcular os divisores de cada número, determinar a sua intersecção, e verificar se esta se reduz ao número 1.
c) acumular os resultados parciais
01.
def
primos_entre_si(limite):
02.
"""Devole a lista dos pares de números que são primos entre so."""
03.
# Acumula resultado
04.
resultado
=
[]
05.
# Gera pares a testar
06.
for
i
in
range(limite):
07.
for
j
in
range(limite):
08.
# testa
09.
if
pes(i,j):
10.
resultado.append((i,j))
11.
# Devolve resultado
12.
return
resultado
Este código traduz a nossa estratégia. É simples e claro. Mas mais do que isso: torna explícito algumas decisões. Por exemplo, que vamos acumular os resultados numa lista. Como princípio de projecto estas decisões devem ser tomadas o mais tarde possível. O ponto b) está concentrado numa chamada de função (pes). Mas nem tudo são rosas no código apresentado. Basta ter consciência de uma ineficiência: analisa pares de números idênticos, (n,n), ou repetidos, (n,m) e (m,n). Este problema pode ser tratado agora ou mais tarde, pois não interfere com o resto do programa. Este facto, poder deixar para mais tarde, é sempre um sinal de que o programa está a ser bem desenhado! Passemos então à a questão de saber se dois números são primos ente si (função pes). Aqui entra o conhecimento que temos sobre o domínio: dois números são primos entre si se o seu máximo divisor comum for 1.
1.
def
pes(m,n):
2.
"""Primos entre si?"""
3.
return
mdc(m,n)
=
=
1
E agora, para concluir, o programa que calcula o máximo divisor comum. Uma vez mais vem em nosso auxílio o conhecimento que temos sobre o Algoritmo de Euclides:
1.
def
mdc(m,n):
2.
"""Algoritmo de Euclides para o máximo divisor comum."""
3.
if
n
=
=
0
:
4.
return
m
5.
else
:
6.
return
mdc(n, m
%
n)
É recursivo! Mas podemos facilmente propor uma solução iterativa, se não estamos confortáveis com a recursividade:
1.
def
mdc_iter(m,n):
2.
while
n >
0
:
3.
m, n
=
n, m
%
n
4.
return
m
Podemos também ser tentados a usar:
1.
def
mdc_iter_b(m,n):
2.
while
n:
3.
m, n
=
n, m
%
n
4.
return
m
A diferença entre estas duas últimas soluções é ténue. Mas não gostamos da última. O código fica menos claro e mais dependente do modo como a linguagem Python interpreta a condição Falso.
Por falar da linguagem, e do conhecimento que se tem sobre a linguagem, podíamos usar como solução para o cálculo do máximo divisor comum:
1.
def
mdc_py(m,n):
2.
from
fractions
import
gcd
3.
return
gcd(m,n)
A partir da versão 2.6 apareceu o novo tipo Fraction. Como sabemos, a simplificação de fracções passa por saber o máximo divisor comum. Por isso o módulo usa esta operação.
Regressemos ao programa principal para corrigir as ineficiências. Sabemos o que fazer para evitar repetições:
01.
def
primos_entre_si_b(limite):
02.
"""Devolve a lista dos pares de números que são primos entre so."""
03.
# Acumula resultado
04.
resultado
=
[]
05.
# Gera pares a testar
06.
for
i
in
range(
1
,limite):
07.
for
j
in
range(i
+
1
,limite):
08.
# testa
09.
if
pes(i,j):
10.
resultado.append((i,j))
11.
# Devolve resultado
12.
return
resultado
13.
14.
def
pes(m,n):
15.
"""Primos entre si?"""
16.
return
mdc(m,n)
=
=
1
Foi só mexer no comando range dos dois ciclos for!
Juntemos as peças do puzzle:
01.
# Primos entre si
02.
03.
def
primos_entre_si(limite):
04.
"""Devole a lista dos pares de números que são primos entre so."""
05.
# Acumula resultado
06.
resultado
=
[]
07.
# Gera pares a testar
08.
for
i
in
range(
1
,limite):
09.
for
j
in
range(i
+
1
,limite):
10.
# testa
11.
if
pes(i,j):
12.
resultado.append((i,j))
13.
# Devolve resultado
14.
return
resultado
15.
16.
def
pes(m,n):
17.
"""Primos entre si?"""
18.
return
mdc(m,n)
=
=
1
19.
20.
def
mdc(m,n):
21.
from
fractions
import
gcd
22.
return
gcd(m,n)
23.
24.
if
__name__
=
=
'__main__'
:
25.
print
primos_entre_si(
100
)
E podemos terminar. Ou será que não? Vamos ver o que acontece se eu coloco os números que são primos entre si numa grelha quadrada. Para ficar mais bonito vou usar o módulo cImage. Mas não quero mexer no que fiz: o cálculo e a visualização devem estar bem separados. A estratégia, uma vez mais neste caso, é simples:
a) criar uma imagem vazia com a dimensão do número máximo (do limite)
b) colorir os pixeis nas posições que correspondem a pares de números primos entre si:
01.
from
cImage
import
*
02.
03.
def
mostra_pes(limite,lista_pares):
04.
janela
=
ImageWin(
'Primos'
,limite,limite)
05.
imagem
=
EmptyImage(limite,limite)
06.
pixel_vermelho
=
Pixel(
255
,
0
,
0
)
07.
for
cord_x,cord_y
in
lista_pares:
08.
imagem.setPixel(cord_x,cord_y,pixel_vermelho)
09.
imagem.setPixel(cord_y,cord_x,pixel_vermelho)
10.
imagem.draw(janela)
11.
janela.exitOnClick()
Ligamos as duas partes:
1.
def
main(limite):
2.
lista
=
primos_entre_si(limite)
3.
mostra_pes(limite,lista)
Notar como restaurámos a matriz completa, incluindo os pares (x,y) e (y,x)). E executamos!

Que bela textura obtida a partir da matemática... olhem bem para a imagem e descubram o padrão. Há uma ordem escondida!!
Vimos que era importante ter conhecimento sobre o domínio mas também sobre a linguagem. Além disso, com rigor e disciplina, avançando passo a passo, não procurando resolver todo o problema de uma só vez, construímos uma solução simples, clara, eficiente, reutilizável. Arte, Design, Ciência e Engenharia.


(*) As imagens são do excelente livro: The Plenitude: Creativity, Innovation, and Making Stuff, Rich Gold, MIT Press, 2007.
Sem comentários:
Enviar um comentário