segunda-feira, 28 de outubro de 2013
A causa das coisas (II)
Nunca paramos de admirar os feitos desportivos dos grandes desportistas, seja no futebol, no golfe, no xadrez ou em qualquer outra actividade. A que se deve o seu tão elevado desempenho? Uns dirão que é o resultado da genética, ou seja uma predisposição natural para serem excelentes no que fazem. Seguramente que uma “boa” genética ajuda. Mas... se os ouvirmos falar, eles referem em primeiro lugar uma coisa mais simples: treino, muito treino.
No mundo da Ciência e da Tecnologia as coisas não são muito diferentes. A demonstração do último Teorema de Fermat (http://en.wikipedia.org/wiki/Fermat's_Last_Theorem) por Andrew Wiles no final do século passado mostra um cientista que durante cerca de 8 anos se dedicou à sua demonstração.Uma tarefa que muitos (todos, menos ele?) julgavam impossível. E conseguiu. Steve Jobs foi conhecido pela forma como nunca desistia de tornar possível um novo artefacto, que muitos (todos, menos ele?) julgavam impossível, fosse ele um iPhone ou um Mac Book Air. E conseguiu.
Dedicação, trabalho árduo são as chaves para o sucesso. Há muitos anos, quando assistia em Paris a uma conferência por um dos pais fundadores da Inteligência Artificial, Marvin Minsky, ele começou a palestra dizendo: “Somos todos Einsteins”. Não é verdade, todos o sabemos. Mas o que é verdade, como disse um dia Lou Reed, ontem falecido, independentemente do nosso ponto de partida, “se praticares uma coisa, uma, outra e outra vez, é suposto ires melhorando.”
Programar não é uma actividade contemplativa.
Take a walk on the wild side!
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ê?
sábado, 26 de outubro de 2013
Acrónimos
Numa das aulas foi pedido um programa para resolver o problema de gerar um acrónimo a partir de uma cadeia de caracteres. Por exemplo, “Ernesto Jorge Fernandes Costa” deve dar como resultado “EJFC”. Os acrónimos devem ser sempre em maiúsculas. No caso de “random access memory” o resultado deverá ser “RAM”.
Alguns resolveram o problema de um modo simples: percorrer a cadeia à procura do espaço em branco, que separa as palavras, para escolher o caractere seguinte. Vamos ver o código.
def acronimo_alunos(cadeia):
"""Extrai o acrónimo da cadeia."""
cadeia = cadeia.upper()
acro = cadeia[0]
for i in range(1, len(cadeia)-1):
if cadeia[i] == ' ':
acro = acro + cadeia[i+1]
return acro
O programa começa por converter toda a cadeia para maiúsculas. De seguida começa por formar o acrónimo com o primeiro caractere da cadeia que, por definição, será sempre o primeiro caractere da primeira palavra. Dentro do ciclo procura o espaço em branco e vai buscar o caractere seguinte para juntar ao acrónimo. Testando com os dois exemplos acima dá o resultado esperado. Perfeito! Perfeito??
E se existirem caracteres brancos no início ou no final? E se existir mais do que um espaço em branco a separar as palavras? Se testar verificará que a sua solução cai por terra. Enfim, cai por terra no caso de ser possível acontecer o descrito. Vamos admitir que sim e procurar uma nova solução.
def acronimo(cadeia):
"""
Constrói o acrónimo a partir de uma frase
representada por uma cadeia de caracteres.
"""
acro = ''
inicio = True
for car in cadeia:
if car == ' ':
inicio = True
elif inicio == True:
acro += car.upper()
inicio = False
return acro
O que fizemos? Algo de muito simples: usamos um indicador booleano (os ingleses dizem uma flag - bandeira) para nos dizer quando estamos à procura do início de uma palavra. Isso é verdade enquanto não encontrarmos um caractere diferente do branco! Quando isso acontece juntamos o caractere depois de o converter para maiúsculas associando-o ao nome acro que funciona como um acumulador. Experimente agora e verá que funciona para todas as situações.
Existem outras soluções para o problema. A primeira que vamos mostrar introduz implicitamente conceitos que ainda não demos (no caso objectos do tipo lista, ako de tuplos mas mutáveis).
def acronimo(cadeia):
acro = ''
nova_cadeia = cadeia.strip().split()
for pal in nova_cadeia:
acro += pal[0].upper()
return acro
Nesta solução começamos por tirar os espaços em branco nas extremidades esquerda e direita da cadeia (método strip) e dividimos o que fica na sequência das palavras que a constituem (método split). Agora, no ciclo for, apenas temos que ir buscar o primeiro caractere de cada palavra, passá-lo a maiúsculas e juntar tudo. Simples, não é?
A última solução que vamos apresentar utiliza o conceito de ciclo variável (ciclo while) que será discutido numa das próximas aulas.
def acronimo(frase):
""" Forma um acronimo a partir da frase."""
frase = frase.upper().strip()
comprimento = len(frase)
acron = ''
posicao = 0
while posicao < comprimento:
acron = acron + frase[posicao].upper()
while (posicao < comprimento) and (frase[posicao] != ' '):
posicao = posicao + 1
while (posicao < comprimento) and (frase[posicao] == ' '):
posicao = posicao + 1
return acron
Começamos por passar tudo para maiúsculas e retirar os espaços em branco nas extremidades. De seguida vamos percorrer posição a posição a cadeia. Sabemos que o primeiro caractere da cadeia sem os espaços na extremidade tem que fazer parte do acrónimo. Depois passamos por todos os caracteres significativos até aparecer um espaço em branco (primeiro ciclo while interior). De seguida passamos por cima dos espaços em branco (segundo ciclo while interior) e voltamos ao início do ciclo while principal. Se ainda existirem caracteres ele terá que ir de novo para o acrónimo pelo que o ciclo se repete.
Moral da História: (1) Nem sempre o que parece é! (2) Vários são os caminhos que nos levam a Roma!
segunda-feira, 21 de outubro de 2013
Erros, equívocos e outras singularidades (I)
Depois de corrigir testes e/ou exames tenho por hábito, com mais calma, olhar para as vossas respostas na procura dos problemas que ainda subsistem. Todos aprendem com os erros, mesmo com os erros dos outros. No caso dos docentes isso serve para procurar melhorar o modo como ensinam. Vou tentar referir aqui no blogue algumas das situações que nos podem ajudar a ser melhores programadores.
Começo com o problema do teste da TP9 que pedia para calcular a Distância de Hamming entre duas cadeias de caracteres, isto é, o número de posições em que os caracteres das duas cadeias são diferentes. Vejamos uma das soluções que apresentaram. Trata-se de uma solução muito próxima da melhor solução.
def dist_ham(cad_1, cad_2):
res = 0
indice = 0
if len(cad_1) > len(cad_2):
return False
for car in cad_1:
if car != cad_2[indice]:
res = res + 1
indice = indice + 1
return res
Este programa funciona para cadeias de igual comprimento. Mas o que acontece se a segunda cadeia for maior do que a primeira? Com esta solução, entra no ciclo na mesma quando me parece que a ideia era a de que não fosse assim: @ autor@ queria dizer que se fossem diferentes então devia dar False. Então, uma primeira correcção seria:
def dist_ham(cad_1, cad_2):
res = 0
indice = 0
if len(cad_1) != len(cad_2):
return False
for car in cad_1:
if car != cad_2[indice]:
res = res + 1
indice = indice + 1
return res
Claro que, o que o enunciado pedia implicitamente era para tratar também esse caso, considerando que os caracteres em excesso também deviam ser contados como diferentes. Mas vamos esquecer esse detalhe e olhar para outro aspecto do código menos conseguido.
Nas aulas foi referido que num ciclo for as sequências podem percorridas por posição/índice ou por conteúdo. A opção depende do problema. No exemplo acima estão a ser usadas ambas! É evidente que temos que comparar caracteres e esse facto pode levar a pensar em percorrer as cadeias por conteúdo. Mas temos que comparar os caracteres de cada cadeia numa dada posição. E isso obriga a cada momento qual a posição dos caracteres que queremos comparar. Neste caso, é esta situação que deve prevalecer. Daí a nova versão:
def dist_ham(cad_1, cad_2):
res = 0
if len(cad_1) != len(cad_2):
return False
for indice in range(len(cad_1)):
if cad_1[indice] != cad_2[indice]:
res = res + 1
return res
Uma vez mais, a solução acima não responde satisfatoriamente no caso das cadeias de comprimento diferente. Em post anterior já mostrei como se podia resolver o problema, mesmo quando as cadeias têm comprimentos diferentes.
Conclusão maior: não é boa prática misturar duas formas de percorrer uma sequência. Mas se tal for mesmo necessário há um modo Pythoniano de o fazer recorrendo à função enumerate. Exemplo:
>>> cadeia = 'abcdef'
>>> for indice, valor in enumerate(cadeia): # <-- enumerate!!
... print('indice %d:\tcaractere= %s' % (indice, valor))
...
indice 0: caractere= a
indice 1: caractere= b
indice 2: caractere= c
indice 3: caractere= d
indice 4: caractere= e
indice 5: caractere= f
That’s it!
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*aE 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, inIsto 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:builtins.TypeError: 'module' object is not callable
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, inParece 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:builtins.TypeError: raiz_quadrada() takes no arguments (1 given)
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.966630Fantá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.

sábado, 19 de outubro de 2013
Problemas 3.15, 3.16 e 3.17
Se percebemos a solução para o Problema 3.12 então estes também não oferecem grande dificuldade. Comecemos pelo ... primeiro.
import turtle
def adn_tartaruga(tartaruga, adn):
""" Simula o comportamento da tartaruga ditado pelo seu ADN."""
for car in adn:
if car == 'f':
tartaruga.fd(50)
elif car == 't':
tartaruga.bk(50)
elif car == 'd':
tartaruga.rt(45)
else:
tartaruga.lt(45)
if __name__ == ‘__main__’:
tarta = turtle.Turtle()
adn_1 = ‘ffttedtftedf’
adn_tartaruga(tarta,adn_1)
turtle.exitonclick()
O que é que a solução tem de especial? Muito pouco. Os valores do movimento e das rotações são fixos. Por outro lado, usamos o construtor do módulo turtle para definir uma tartaruga concreta em vez de usar a tartaruga genérica. Finalmente, o ADN da tartaruga foi por nós definido “à mão” . A primeira variante, Problema 3.16, pede-nos que os valores do movimento e das rotações não sejam fixos mas possam variar dentro de valores razoáveis.
import random
def adn_tartaruga_alea(tartaruga, adn):
""" Simula o comportamento da tartaruga ditado pelo seu ADN."""
for car in adn:
lado = random.randint(20,100)
angulo = random.randint(10,180)
if car == 'f':
tartaruga.fd(lado)
elif car == 't':
tartaruga.bk(lado)
elif car == 'd':
tartaruga.rt(angulo)
else:
tartaruga.lt(angulo)
Como se observa a adaptação às novas condições foi fácil. Finalmente, no problema 3.17, apenas nos é pedido que o ADN da tartaruga seja ele próprio gerado aleatoriamente. Isso foi o que aprendemos a fazer com a solução ao Problema 3.12.
import random
def adn_tartaruga_total(tartaruga, passos):
""" Simula o comportamento da tartaruga em função do seu ADN. O dito
ADN é gerado aleatoriamente."""
adn =''
for i in range(passos):
adn = adn + random.choice('fted')
adn_tartaruga_alea(tartaruga,adn)
def adn_tartaruga_alea(tartaruga, adn):
""" Simula o comportamento da tartaruga ditado pelo seu ADN."""
for car in adn:
lado = random.randint(20,100)
angulo = random.randint(10,180)
if car == 'f':
tartaruga.fd(lado)
elif car == 't':
tartaruga.bk(lado)
elif car == 'd':
tartaruga.rt(angulo)
else:
tartaruga.lt(angulo)
That's it!
Problema 3.10
Vamos resolver o problema 3.10 do livro que nos pede para gerar uma cadeia de ADN. O único argumento/parâmetro formal é o tamanho da sequência pretendido. Trata-se de um exercício muito fácil, mas que nos pode dar alguns ensinamentos.
A ideia da solução passa por repetir tantas vezes quantas o tamanho da sequência a escolha aleatória de uma das bases presentes na cadeia de ADN, de entre as quatro possíveis (Adenina (A), Citosina (C), Guanina (G) e Timina (T). Os resultados vão sendo acumulados. Daí a solução.
import random
def gera_adn(tam):
"""Gera uma cadeia de ADN de tamanho tam. Padrão ciclo-acumulador"""
adn =''
for i in range(tam):
base = random.choice('TACG')
adn = adn + base
return adn
Baseia-se esta solução no padrão ciclo (neste caso ciclo for) e acumulador (o nome adn onde vamos acumulando os resultados).
Suponhamos agora uma situação semelhante. Alguém anda perdido numa cidade, perfeitamente geométrica, e pede ajuda para se deslocar a um dado local da cidade. Recebe ajuda na forma de uma sequência de indicações do tipo Avança (A), Recua (R), Vira à Esquerda (E) ou Vira à Direita (D). Admitamos que o que se pretende é um programa que gere sequências válidas de indicações, de tamanho variável. O leitor não estranhará, depois de uma breve reflexão, que a solução proposta não lhe ofereça grandes dúvidas.
mport random
import random
def gera_comandos(tam):
"""
Gera uma sequências de indicações de movimento numa cidade de tamanho tam.
Padrão ciclo-acumulador
"""
seq_comandos =''
for i in range(tam):
comando = random.choice('ARED')
seq_comandos = seq_comandos + comando
return seq_comandos
Olhando para estas soluções verificamos que o que as distingue é apenas os caracteres possíveis nos dois casos. Podemos então abstrair esse detalhe, e construir uma solução genérica que sirva para gerar qualquer sequência de caracteres, conhecido o alfabeto dos caracteres possíveis. Vamos a isso.
import random
def gera_comandos(tamanho, alfabeto):
""" Gera uma sequência de comandos com elementos retirados aleatoriamente do alfabeto."""
comandos = ''
for i in range(tamanho):
comandos = comandos + random.choice(alfabeto)
return comandos
Mais uma vez verificamos que generalizar significa transformar uma constante (no caso a cadeia de ADN ou de indicações) num nome que aparece como parâmetro formal.
Mas será que as versões concretas devem ser deitadas fora? Não necessariamente. Suponha que tem indicações sobre a probabilidade de ocorrência de um dos eventos. Como incorporar isso no programa. Vamos ver para o caso da indicação do percurso. Se a pessoa for competente então é provável que dê mais indicações de Avançar do que de Recuar e que privilegie também uma das indicações de Virar. Vamos ver como podemos incorporar essa indicação no código.
import random
def gera_comandos(n):
"""Gera n comandos aleatoriamente. Alguns movimentos são mais prováveis do que outros."""
comandos = ''
for i in range(n):
if random.choice([0,0,0,1]) == 0:
comandos += random.choice(['A','A', 'A','A','R'])
else:
comandos += random.choice(['E',’E’, 'D'])
return comandos
Neste exemplo, avançar e recuar globalmente têm 75% de probabilidades de ocorrer, enquanto virar tem apenas 25%. Dentro de cada caso também há diferenças: avançar tem 80% de probabilidade de ocorrer e virar à esquerda 66%.
Subscrever:
Mensagens (Atom)






