sexta-feira, 5 de fevereiro de 2010

Reflexões (sobre o exame de recurso)

Deixo aqui algumas reflexões suscitados pelo exame de recurso.

Em primeiro lugar, continuo a achar estranho que se entregue um ponto para corrigir quando somando a cotação das perguntas respondidas é impossível ter nota para passar. O único resultado disso é mais tempo para corrigir e, consequentemente, atrasar a divulgação dos resultados. Também me choca ver casos de pessoas que respondem a tudo mas têm zero no final. Infelizmente não são poucos.

Em segundo lugar, a quantidade de erros de português, erros lexicais, de sintaxe e de semântica é muito preocupante. O pouco cuidado na apresentação também é regra.

Em terceiro lugar, confunde-me que alunos do ensino superior não consigam interpretar/entender o enunciado. Por exemplo, se eu coloco ??? numa listagem, isso não é a saída do interpretador! Que não entendam quando se pede uma resposta geral ou uma resposta específica. Se eu coloco um exemplo com os vectores [1,2,3] e [4,8,12] (pergunta 3) não é porque queira um programa para calcular a diferença absoluta daqueles vectores apenas. O mesmo se aplica quando apresenta um tabuleiro 4 X 4. Todos estes aspectos estavam bem clarificados no enunciado de cada pergunta!!

Finalmente, registo a vossa dificuldade em dominar a complexidade de resolver um problema. Vamos por isso às perguntas concretas, primeiro as teóricas, depois as de programação.

Pergunta 1

Um conceito nuclear em programação é o de objecto e das suas características. Chegar ao final do ano e ainda não saber que os objectos têm identidade, valor e tipo e que é com base nestas características que se distinguem os que são mutáveis dos que não são.

Pergunta 2

Quem não tiver a noção do que é um tipo de dados (objecto + operações) não consegue responder. O erro mais comum foi dizer que são equivalentes pois posso transformar um ficheiro numa cadeia de caracteres. Mas assim deixamos de estar a operar com um ficheiro...

Pergunta 3

Esta pergunta (que valia 25% da nota!) marca a fronteira de quem merece e de quem não merece passar! Posso admitir (embora estranhe) que têm umas convicções matemáticas bizarras relativamente a operações com módulos. Tratava-se de efectuar a soma do módulo das diferenças componente a componente. Não se tratava, por exemplo, de somar todos os valores de um vector, somar todos os valores de outro vector, fazer a diferença e depois tomar o valor absoluto do resultado. E que posso dizer de quem calcula o máximo apenas sobre um valor? Ao fim de um ano, não posso aceitar que não consigam fazer um programa que envolve um acumulador e um ciclo.

Pergunta 4

No exame da época normal tinha parecido um problema parecido: passar informação de um ficheiro para um dicionário. Deixei aqui no blogue vários posts sobre o assunto. Mas mesmo assim...

Todos os problemas envolvem conhecimento sobre a linguagem de programção e sobre o processo de construção da solução. Este não foge à regra. A melhor maneira de dominar a complexidade de um problema é abordar a solução de modo progressivo, procurando só tomar decisões concretas quando não há alternativa. Vejamos neste caso. Num olhar global, a estratégia é fácil de enunciar: (a) ler o ficheiro linha a linha; (b) extrair a informação relevante; (c)colocar no dicionário. Daí o esboço do programa:

def dic_max_temp_b(ficheiro):
""" Do ficheiro ao dicionário."""
# inicialização
# ---- leitura
# lê linha
while # há informação:
# extrai informação
# coloca no dicionário
# lê linha
# finaliza
return # resultado

A inicialização consiste em abrir o ficheiro e criar o dicionário vazio. No final, fechar o ficheiro.

def dic_max_temp_b(ficheiro):
""" Do ficheiro ao dicionário."""
# inicialização
f_in = open(ficheiro, 'r')
dic = {}
# ---- leitura
# lê linha
while # há informação:
# extrai informação
# coloca no dicionário
# lê linha
# finaliza
f_in.close()
return dic


A leitura tem que ser feita linha a linha, pois é assim que a informação relevante está organizada e é assim que terá que ser processada:

def dic_max_temp_b(ficheiro):
""" Do ficheiro ao dicionário."""
# inicialização
f_in = open(ficheiro, 'r')
dic = {}
# ---- leitura
# lê linha
linha = f_in.readline()

while # há informação:
# extrai informação
# coloca no dicionário
# lê linha
linha = f_in.readline()
# finaliza
f_in.close()
return dic

Já só falta extrair a informação e colocar no dicionário. Quantos elementos existem por linha? Três: o nome do produto, o número da experiência e o valor da temperatura. Tendo o cuidado de saber que é preciso retirar a marca de fim de linha podemos obter e separar a informação contida numa linha com uma única instrução:

def dic_max_temp_b(ficheiro):
""" Do ficheiro ao dicionário."""
# inicialização
f_in = open(ficheiro, 'r')
dic = {}
# ---- leitura
# lê linha
linha = f_in.readline()
while # há informação:
# extrai informação
cod,num,temp = linha[:-1].split()
# coloca no dicionário
# lê linha
linha = f_in.readline()
# finaliza
f_in.close()
return dic

Esta forma de fazer é genérica. Quem não partiu a linha nos seus constituintes e manteve a cadeia de caracteres tinha o problema de saber em que posições começava e acabava cada elemento. E isso deu azo a muita confusão. Imaginem que quem colocou a informação no ficheiro coloca diferentes espaços entre elementos? Ou que o número de experiências é maior do que dez? Ou que a temperatura pode vir antes do número da experiência? A solução de manter uma cadeia de caracteres ainda funciona? Se não (o que é verdade!) que alterações é preciso fazer no código?

Agora já só falta colocar a informação no dicionário. Temos duas situações: ou já lá está algo, ou não. O primeiro caso ainda se subdivide em dois: ou o que já existe é maior do que o novo valor ou não: Primeira divisão:

if dic.has_key(cod):
...
ou (porque o método has_key já não existe em Python 3.0)

if cod in dic.keys():
...

ou ainda

if dic.get(cod, False):
...

E vamos à segunda divisão:

if cod in dic.keys():
if dic[cod] < int(temp):
# substitui
else:
# introduz

que dá origem a:

if cod in dic.keys():
if dic[cod] < int(temp):
dic[cod] = int(temp)
else:
dic[cod] = int(temp)

E notamos duas coisas. Uma, importante, é que temos que fazer a conversão para inteiros. Experimente testar se ‘4’ > ’10’ e perceberá a razão! Outra, é o facto de andarmos com código repetido. Nesta última situação, ajuda saber um pouco de dicionários em Python. Tudo se pode fazer numa linha de código.

def dic_max_temp_b(ficheiro):
""" Do ficheiro ao dicionário."""
# inicialização
f_in = open(ficheiro, 'r')
dic = {}
# ---- leitura
# lê linha
linha = f_in.readline()
while # há informação:
# extrai informação
cod,num,temp = linha[:-1].split()
# coloca no dicionário
dic[cod] = max(dic.get(cod,-100),int(temp))
# lê linha
linha = f_in.readline()
# finaliza
f_in.close()
return dic

Pergunta 5

Esta pergunta tinha algumas dificuldades: (a) saber posicionar a tartaruga para cada quadrado; (b) em cada momento saber a cor do quadrado. Mas estas dificuldades eram fáceis de ultrapassar se se lembrassem que tínhamos feito um problema exactamente igual na ficha sobre imagens! Muito me surpreendeu ainda tantos alunos não perceberem que deviam usar o programa para desenhar um quadrado de uma dada cor de forma autónoma. Isto significa que não conseguem pensar no problema dividindo-o em partes mais simples, como ainda fizemos para o caso do problema 4. Vamos ver então como se resolve a questão. Em primeiro lugar tomamos a decisão de imprimir linha a linha:


def tabuleiro(tartaruga,n,lado):
# inicialização
for l in range(n):
# imprime linha l

A inicialização consiste apenas em colocar a tartaruga numa dada posição inicial. Essa posição tanto podia ser comunicada como parâmetro, como podia ser definida em termos absolutos no interior do programa. Segunda questão: como desenhamos uma linha? Decisão: quadrado a quadrado:

def tabuleiro(tartaruga,n,lado):
# inicialização
for l in range(n):
# desenha linha l
# inicialização da linha
for c in range(n):
# desenha coluna c

Neste momento, não podemos adiar mais a decisão de saber a cor. Podemos ter uma solução que só depende da alternância de cor dentro de uma linha. Mas, se formos por aí, não nos podemos esquecer que as linhas também alternam a sua cor de início! Outra solução, melhor, é tomar a decisão em termos da linha e da coluna. Basta perceber que sempre que a soma dos índices da linha e da coluna for par o quadrado é branco:

def tabuleiro(tartaruga,n,lado):
# inicialização
for l in range(n):
# desenha linha l
# inicialização da linha
for c in range(n):
# desenha coluna c
if (l + c) % 2 == 0:
# quadrado branco
else:
# quadrado preto

E como se desenha o quadrado? Fazendo uso do programa fornecido:

def tabuleiro(tartaruga,n,lado):
# inicialização
for l in range(n):
# desenha linha l
# inicialização da linha l
for c in range(n):
# desenha coluna c
if (l + c) % 2 == 0:
quad(tartaruga, lado,‘white’)
else:
quad(tartaruga,quad,‘black’)

Como estamos a ver, das duas dificuldades à pouco enunciadas, só resolvemos a da cor. Falta a da posição! No início será (0,0). Esta decisão tem consequências? Claro: sempre que mudarmos de linha temos que colocar a tartaruga na posição (0, linha). Como podemos fazer isso? De modo relativo ou de modo absoluto. De modo relativo: tendo em atenção quanto avançámos mandamos a tartaruga recuar igual valor. De modo absoluto: usando o comando setx(0). Mas e o valor da coordenada da linha, necessário sempre que mudamos de linha? Também não é difícil de ver que deve ser o valor que tinha mais o valor do lado:

def tabuleiro(tartaruga,n,lado):
# inicialização
tartaruga.pu()
tartaruga.goto(0,0)
tartaruga.pd()
for l in range(n):
# desenha linha l
# inicialização da linha l
tartaruga.setx(0)
tartaruga.sety(tartaruga.ycor() + lado)
# desenha linha
for c in range(n):
if (c + l) % 2 == 0:
quad(tartaruga,lado,'white')
else:
quad(tartaruga,lado,'black')

Claro que também se podia usar tartaruga.sety(l * lado). Mas assim ainda não avançamos coluna a coluna?! Pois não. Por cada quadrado desenhado temos que avançar o valor do lado:

def tabuleiro(tartaruga,n,lado):
# inicialização
tartaruga.pu()
tartaruga.goto(0,0)
tartaruga.pd()
for l in range(n):
# desenha linha l
# inicialização da linha l
tartaruga.setx(0)
tartaruga.sety(tartaruga.ycor() + lado)
# desenha linha
for c in range(n):
if (c + l) % 2 == 0:
quad(tartaruga,lado,'white')
else:
quad(tartaruga,lado,'black')
tartaruga.setx(tartaruga.xcor() + lado)

Esta solução é simples e elegante. Podemos facilmente alterar o programa para que o tabuleiro não seja do tipo n X n,outras cores, desenho por colunas em vez de ser por linhas. Mudar de forma é que é mais complexo. Mas nas nossas experimentações íamos descobrir é que não protegemos devidamente o reposicionamento da tartaruga: em certas situações ficam um rasto visível. Mas até isso se resolve sem grande dificuldade.

def tabuleiro_b(tartaruga,pos,n1,n2,lado, cor_1, cor_2):
tartaruga.pu()
tartaruga.goto(pos)
tartaruga.pd()
for c in range(n1):
# reposiciona
tartaruga.pu()
tartaruga.sety(pos[1])
tartaruga.setx(tartaruga.xcor() + lado)
tartaruga.down()
# desenha coluna
for l in range(n2):
if (c + l) % 2 == 0:
quad(tartaruga,lado,cor_1)
else:
quad(tartaruga,lado,cor_2)
tartaruga.pu()
tartaruga.sety(tartaruga.ycor() + lado)
tartaruga.down()

Divirta-se!

Sem comentários:

Enviar um comentário