domingo, 20 de outubro de 2013

A Causa das Coisas (I)

Programar não é um acto isolado. Programamos muitas vezes em equipa e o que fazemos é para ser usado por nós e por quem necessitar. Agora que sabemos que em Python existem módulos que estendem a linguagem dando-nos novas possibilidades e que foram desenvolvidos por alguém por esse mundo fora, sabemos que é assim: os programas são feitos por muitos para serem usados por muitos. Esse facto tem consequência importantes para o modo como desenvolvemos e disponibilizamos o código. Ele deve estar correcto e ser eficiente. Claro. Também deve ser elegante, fácil de manter e adaptar. Seguramente. Mas, não menos importante, deve ser possível integrar o código noutros programas sem problemas.
Suponhamos que queremos desenvolver um programa para calcular a raiz quadrada de um número bem preciso, por exemplo 5. Sabemos que em Python isso não é problema pois alguém resolveu essa questão para nós, graças ao método sqrt do módulo math. Mas vamos admitir que não é assim. Vamos então à procura de um algoritmo. Fazemos uma pesquisa na internet recorrendo ao Google, e lá nos aparece o Método de Newton. Para calcular o valor aproximado da raíz basta iterar a fórmula:
x(n+1) = 1/2 * (x(n) + a/x(n))
onde x denota a raiz do número a. Ligamos o computador, usamos o nosso IDE preferido para Python, e escrevemos o programa.
x = 2.0

for i in range(10):
    x = 1/2 * (x + 5/x)   
print(x)
Executado o programa lá nos aparece o lindo valor de : 2.23606797749979, que compara excepcionalmente bem com o que se obtém usando math.sqrt(5). Mas se o número for 20 em vez de 5? Não há problema, alteramos ligeiramente o código.
x = 4.0

for i in range(10):
    x = 1/2 * (x + 20/x)    
print(x)
Uma vez mais o resultado é excelente. Mas, depois de pensarmos um bocado chegamos sem problema à conclusão que o melhor é escrever um pedaço de código único que possa ser utilizado para todas as situações e que minimize as alterações que são necessárias introduzir. Vamos a isso.
a = eval(input('Qual o número?  '))
x = a/2

for i in range(10):
    x = 1/2 * (x + a/x)    
print(x)
Agora cada vez que o código é executado pede ao utilizador o número. Podemos agora dormir descansados: sempre que for preciso nós calcularmos a raiz quadrada de um número é só executar este código. E como não somos egoístas, quando um amigo nosso teve o mesmo problema não tivemos dúvida em lhe passar o ficheiro raiz2.py com o código, dizendo-lhe que só precisava ou de mandar correr o ficheiro ou de importar o código, dependendo do que queria fazer.
Python 3.2.3 (default, Sep  5 2012, 20:52:27) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)]
Type "help", "copyright", "credits" or "license" for more information.
>>> import raiz2
Qual o número?  15
3.872983346207417
>>>
Como se vê, mal importou o módulo apareceu a pergunta, o nosso amigo lá colocou o número e aparece resplandecente o resultado. Perfeito!
Mas uns dias depois o amigo aparece-lhe de novo, muito triste. Queria usar o seu programa do cálculo da raíz quadrada como auxiliar de outro programa para calcular as raízes de um polinómio do segundo grau, e não via como. Ele até sabe a fórmula resolvente:
raiz_1 = (-b + raiz2(b**2 - 4*a*c))/ 2*a
raiz_2 = (-b - raiz2(b**2 - 4*a*c))/ 2*a
E fez mesmo um programa:
import raiz2

a = eval(input('Coeficiente de grau 2: '))
b = eval(input('Coeficiente de grau 1: '))
c = eval(input('Coeficiente de grau 0: '))

raiz_1 = (-b + raiz2(b**2 - 4*a*c))/ 2*a
raiz_2 = (-b - raiz2(b**2 - 4*a*c))/ 2*a

print(raiz_1, raiz_2)
Mas quando o programa é executado, ele começa logo por me pedir o número cuja raiz quero saber, só depois pede os coeficientes e no fim ainda me dá um erro que não se entende lá muito bem.
Python 3.2.3 (default, Sep  5 2012, 20:52:27) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)]
Type "help", "copyright", "credits" or "license" for more information.
[evaluate poli2.py]
>>> Qual o número?  13
3.6055512754639896
Coeficiente de grau 2: 2
Coeficiente de grau 1: 3
Coeficiente de grau 0: 4
Traceback (most recent call last):
  File "/Volumes/Work/__Aulas/_____Aulas 2013_2014/IPRP/Blogue_iprp/causas_das_coisas/poli2.py", line 9, in 
builtins.TypeError: 'module' object is not callable
Isto não faz sentido! É que o valor da raíz é função do valor dos coeficientes. Conhecido estes o valor cuja raíz queremos calcular fica determinado. Não somos nós que devemos entrar o valor ou que tem que fazer as contas! E colocar a importação depois de pedir os coeficientes também não resolve, porque o programa vai continuar a pedir o valor cuja raíz se pretende calcular quando isso, já vimos, não faz sentido.
E ainda há o erro! Mas tu percebes a razão do erro pois lembras-te bem como se pode usar o código (objectos e definições) que se encontra num módulo: tens que colocar o nome do módulo, seguido de um ponto, seguido do nome do objecto ou da definição. Mas neste teu caso como proceder se não há um nome associado ao código que calcula a raiz? Então vamos dar um nome, salvar tudo e voltar a executar. Vejamos as alterações ao teu programa:
def raiz_quadrada():
    a = eval(input('Qual o número?  '))
    x = a/2
    
    for i in range(10):
        x = 1/2 * (x + a/x)    
    print(x)
    

raiz_quadrada()
Tens agora a definição do algoritmo para o cálculo da raíz quadrada (def), seguida do seu uso (raiz_quadrada()). Testas e funciona. Passas de novo ao teu amigo,ele altera o seu código:
import raiz2


a = eval(input('Coeficiente de grau 2: '))
b = eval(input('Coeficiente de grau 1: '))
c = eval(input('Coeficiente de grau 0: '))

raiz_1 = (-b + raiz2.raiz_quadrada(b**2 - 4*a*c))/ 2*a
raiz_2 = (-b - raiz2.raiz_quadrada(b**2 - 4*a*c))/ 2*a

print(raiz_1, raiz_2)
e executa:
Python 3.2.3 (default, Sep  5 2012, 20:52:27) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)]
Type "help", "copyright", "credits" or "license" for more information.
[evaluate poli2.py]
>>>Qual o número?  17
4.123105625617661
Coeficiente de grau 2: 3
Coeficiente de grau 1: 4
Coeficiente de grau 0: 5
Traceback (most recent call last):
  File "/Volumes/Work/__Aulas/_____Aulas 2013_2014/IPRP/Blogue_iprp/causas_das_coisas/poli2.py", line 10, in 
builtins.TypeError: raiz_quadrada() takes no arguments (1 given)
Parece que está tudo na mesma. Começa por pedir um número para a raíz, e não devia, e depois de introduzires os coeficientes aparece outra vez uma mensagem de erro, embora diferente da anterior. Para o primeiro problema tens solução. Lembras-te de te dizerem que todos os ficheiros com código Python e que terminam com a extensão .py podem ser executados ou importados. Se não quisermos que durante a importação uma parte do código seja executada temos que a colocar essa parte dentro da instrução condicional:
if__name__ == ‘__main__’:
   *código_aqui*
Vamos alterar então de novo o código de raiz2.py:
def raiz_quadrada():
    a = eval(input('Qual o número?  '))
    x = a/2
    
    for i in range(10):
        x = 1/2 * (x + a/x)    
    print(x)
    
if __name__ == '__main__':
    print(raiz_quadrada())
E toca a executar o programa para as raizes do polinómio. Verificamos que o primeiro erro desapareceu mas o segundo mantém-se. Então qual é a questão?
Por um lado sabemos que o valor da raiz a calcular é função dos coeficientes e por isso é determinado internamente pelo programa e não por nós através de uma instrução de input. Por outro lado, quando o resultado é calculado não o queremos imprimir mas antes usar dentro de uma expressão mais geral (i.e., (-b + raiz2.raiz_quadrada(b**2 - 4*a*c))/ 2*a)). Por isso o que está mal com a nossa solução é o modo como raiz quadrada recebe o valor e o comunica. Quando não nos interessa a interacção com o utilizador humano o que temos a fazer é usar um parâmetro formal, para a entrada do dado, e return, para devolver o resultado, em vez de input e de print respectivamente. Vamos de novo alterar o código.
def raiz_quadrada(a):
    """Calcula a raiz quadrada aproximada de a, suposto um número positivo."""
    x = a/2
    for i in range(10):
        x = 1/2 * (x + a/x)    
    return x
    
if __name__ == '__main__':
    # Para testar
    num = eval(input('O número sff: '))
    print(raiz_quadrada(num))
Parece em condições de ser usado agora. Vejamos.
Python 3.2.3 (default, Sep  5 2012, 20:52:27) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)]
Type "help", "copyright", "credits" or "license" for more information.
[evaluate poli2.py]
Coeficiente de grau 2: 2
Coeficiente de grau 1: 16
Coeficiente de grau 0: 4
raiz 1 -1.033370
raiz_2 -30.966630
Fantástico!

Mas se olharmos para o código do programa para o cálculo do polinómio, não estamos a incorrer no mesmo erro? Isto é, se alguém quiser usar este código para calcular as raízes de um polinómio não vai ter o mesmo problema? Claro que sim! Então vamos resolver isto do mesmo modo.
import raiz2

def polinomio_2_grau(a,b,c):
    """ Calcula as raízes de um polinómio do segundo grau. Funciona para raízes não complexas."""
    raiz_1 = (-b + raiz2.raiz_quadrada(b**2 - 4*a*c))/ 2*a
    raiz_2 = (-b - raiz2.raiz_quadrada(b**2 - 4*a*c))/ 2*a
    return  (raiz_1, raiz_2)

if __name__ == '__main__':
    # Para testar
    a = eval(input('Coeficiente de grau 2: '))
    b = eval(input('Coeficiente de grau 1: '))
    c = eval(input('Coeficiente de grau 0: '))
    r_1, r_2 = polinomio_2_grau(a,b,c)
    print('raiz 1: %f\nraiz 2: %f' % (r_1, r_2))

Em conclusão. Existem (para já, pois veremos que há ainda outros) dois modos de introduzir dados (parâmetro formais ou instrução input) e dois modos de obter resultados (return ou instrução print). O que usamos depende do tipo do problema e do modo como o código tem que ser integrado, ou não com outro código. Um dia falaremos com mais detalhe sobre os programas em que a interacção com o utilizador humano é muito grande (jogos, por exemplo), e como devemos proceder. 

Sem comentários:

Enviar um comentário