segunda-feira, 25 de janeiro de 2010

Erros que já não deviam existir!!!

Mais uma vez faço uso de um pedido de um aluno para tentar fazer pedagogia. Não sei se é o melhor caminho, até porque pode parecer que estou a reforçar a ideia de que se estuda resolvendo os problemas dos exames. Estes pedidos, que tenho recebido nos últimos dias (afinal o exame está à porta!!), levam-me a questionar sobre o nosso método de ensino e o vosso modo de aprender(?). No vosso caso, é claro que muitos são os que não trabalham ao longo do ano. Na melhor das hipóteses assistem passivamente às aulas e guardam depois uns dias antes do exame para se prepararem. E fazem-no tentando decifrar fora de tempo os acetatos das aulas teóricas, e olhando para as soluções de provas anteriores. No melhor dos casos, tentam resolver à vossa maneira, com imensas dificuldades e com muito não saber. Assim não se aprende! Na melhor das hipóteses passa-se no exame, mas não se aprende. Hoje é véspera de exame e, por isso, vou esquecer as minhas metafísicas.

No exame normal, o problema 4 pedia para ler um ficheiro de texto e construir um dicionário em que as chaves são inteiros e os valores a lista das palavras cujo comprimento é igual ao valor da chave. Este problema obriga a saber o que são e como se podem manipular ficheiros de texto e dicionários. Não é lendo apressadamente durante o exame o Manual do Python que estes conceitos se aprendem! Por isso é melhor praticar com exemplos. Em vários post recentes tratei precisamente de ficheiros de texto e de dicionários... Parece que sem sucesso! Mas vamos à solução que me foi enviada:


def exame(ficheiro):
dic={}
lista=[]
string=''
abrir=open(ficheiro,"r")
string+=abrir.read() #string sera a cadeia de caracteres contida no ficheiro#
lista.append(string.split()) #aqui divido a cadeia de caracteres numa lista e junto-a a 'lista'#

for palavra in range(len(lista)):
dic[palavra]=lista[palavra]
print dic

Dizia, e bem, o vosso colega que não funcionava correctamente, devolvendo apenas um dicionário com uma única chave (0) cujo valor associado era a lista de todas as palavras. Este tipo de erro indicia desde logo onde está o problema. Mas para ter essa percepção é preciso ter experiência de programação (e de erros!). Em vez de dizer já onde está o erro, olhemos para o problema e como o podemos resolver.


Vamos uma vez mais por partes. E neste problema a solução passa por dois subproblemas: (1) obter a lista das palavras e, (2)
a partir da lista das palavras construir o dicionário:


def exame(ficheiro):
# (1) obtém palavras
# (2) forma dicionário
return # dicionário

Primeiro comentário à solução: existem grosso modo dois modos de introduzir informação num programa (pelos parâmetros e por instruções de entrada) e dois modos de comunicar resultados (por return e por instruções de impressão). A escolha depende do problema e do que nos é pedido. Neste caso, era evidente que a entrada de dados era por parâmetro e a saída por return.

Passemos ao primeiro sub-problema. Um ficheiro é uma longa cadeia de caracteres, que vamos ter que ler e dividir em palavras.

# (1) obtém lista palavras
f_in = open(ficheiro, ‘r’)
lista_palavras = f_in.read().split()

Segundo comentário sobre a solução apresentada. Qual o sentido de fazer:

string = ‘’
string += abrir.read()

ou de fazer:

lista = []
lista.append(string.split())

Nenhum! Ainda por cima pode dar origem a erros. As associações entre nomes e objectos são feitas através da operação de atribuição, por exemplo a = 5. Só depois disto é que o nome existe no espaço de nomes, associado a objecto. Neste caso, se quero associar ao nome string a cadeia de caracteres do ficheiro, faço:

string = abrir.read()

e o mesmo para a lista:

lista = string.split()

Neste segundo caso está uma parte do erro do programa. É que, em vez de lista estar associada à lista das palavras, fica associada a uma lista que só tem um elemento que é a lista das palavras! Exemplificando, fica:

lista = [[‘toto’,‘abacadabra’]]

e não

lista = [‘toto’,‘abacadabra’]

uma maneira de resolver o problema seria fazer como no caso de string (feio, como já se disse, mas funcional):

lista = []
lista += string.split()

Tem que se perceber melhor os métodos. E, já agora, outra coisa. Os métodos têm uma sintaxe simples:

<objecto>.<método>(<argumentos>)

Aplico o método ao objecto, usando eventualmente argumentos. Mas qual é o resultado da aplicação?? Um objecto!!! Implicações? Podermos aplicar métodos em cadeia;

<objecto>.<método>(<argumentos>).<método>(<argumentos>)....<método>(<argumentos>)

como no exemplo acima:

lista_palavras = f_in.read().split()

Ao objecto ficheiro aplica-se o método read, que devolve um objecto que é uma cadeia de caracteres à qual se aplica o método split!!!

Agora é tempo de resolver a segunda parte. Para simplificar, vamos supor que não nos preocupam nem as palavras repetidas nem os eventuais sinas de pontuação. A solução que tem isto em conta está no post com a solução do exame.

Então o que temos que fazer:

# (2)
por cada palavra em lista de palavras:
acrescentar a palavra ao dicionário

Temos assim uma estratégia que envolve um ciclo. Quem controla o ciclo é o conteúdo da lista e não os índices (ver outro post recente sobre o assunto). Mas precisamos da chave também! Claro, mas isso é fácil: é que, por definição a chave é igual ao comprimento da palavra. E como acrescentamos? Existe a possibilidade de a chave já existir! Certo. Mas usando um get ( ver post recente sobre o assunto) podemos resolver as duas situações possíveis só com uma instrução. Logo o código:

dic = {}
for palavra in lista_palavras:
comp = len(palavra)
dic[comp] = dic.get(comp,[]) + [palavra]

Pergunta: porque é que, neste caso, temos que fazer primeiro dic = {}, e nos casos anteriores de string e lista não??

Terceiro comentário sobre o código apresentado. O ciclo é controlado pelos índices da lista de palavras (não é por colocar for palavra in ... que palavra é uma palavra!) Então, palavra, vai valer, 0, 1, 2,.... Essas serão as chaves do dicionário criado. Mas não é feita a ligação entre as palavras e o seu comprimento. O que se está a fazer é criar uma associação (chave: valor) = (indice da palavra na lista : palavra). Juntando este erro, ao outro já anteriormente referido e relacionado com o mau uso do append, vai dar algo como:
{0: [‘toto’,‘abacadabra’]}



Juntando agora todas as peças do puzzle:

def exame_ec(ficheiro):
f_in = open(ficheiro,'r')
lista_palavras = f_in.read().split()
dic = {}
for palavra in lista_palavras:
comp = len(palavra)
dic[comp] = dic.get(comp, []) + [palavra]
return dic


E pronto! A complexidade domina-se com método, conhecimento e ... muita prática!

1 comentário:

  1. Obrigado.

    Juntando aos erros iniciais o facto de eu não ter percebido/memorizado o que o problema me pedia, tornou-se deveras mais difícil. Para piorar as coisas, estava a tentar criar um dicionário chaves 1,2,3,4,5 para a 1ª,2ª,3ª,4ª,5ª (...) palavras do ficheiro de texto.

    Daí não conseguir safar-me com a resposta que o professou colocou online, anteriormente.

    ResponderEliminar