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!

Exame de Recurso

P1 a)
O que distingue os objectos mutáveis dos objectos imutáveis?

Resposta

No caso dos objectos mutáveis podemos alterar o valor sem alterar a identidade, nos imutáveis não.

P1 b)

Os ficheiros e as cadeias de caracteres são tipos de dados equivalentes? Isto é: tudo o que posso fazer com um objecto de um dos tipos, posso fazer com um objecto do outro. Justifique a sua opinião.

Resposta
Um tipo de dados remete para objectos e operações comuns com esses objectos. Por exemplo, os inteiros e os floats são tipos de dados equivalentes. O mesmo não se pode dizer dos ficheiros de texto e das cadeias de caracteres. Um ficheiro é uma longa cadeia de caracteres, estruturada em linhas, mutável e armazenada de forma permanente. As cadeias de caracteres não têm estrutura interna, são imutáveis e apenas existem durante a execução do programa onde estão definidas. Quanto às operações elas são diversas. No caso dos ficheiros temos operações de consulta por elemento (read), por linha (readline) ou por linhas (readlines). O acesso para consulta é sequencial, ao contrário das cadeias, em que o acesso é directo (indexação ou fatiamento). A operação de escrita (alteração) existe nos ficheiros (por elemento, por linha). alterar uma cadeia de caracteres obriga a criar um novo objecto.


P2
Imagine a seguinte sessão no interpretador de Python:


>>> x = [] * 4
>>> y = [[] for i in range(4)]
>>> z = [[]] * 4
>>> x
??? <---- (a)
>>> y
??? <---- (b)
>>> z
??? <---- (c)
>>> x == y
??? <--- (d)
>>> y == z
??? <---- (e)
>>>


Indique, justificando, o resultado devolvido pelo interpretador nas posições assinaladas por ???.

Resposta

O operador de *, quando um dos operandos é numérico e o outro uma sequência, significa repetição. Repete-se o conteúdo, devolvendo-se o resultado dentro da sequência.


(a) []
(b) [[],[],[],[]]
(c) [[],[],[],[]]
(d) False
(e) True

O operador == significa igualdade dos valores e não igualdade das identidades. Isso explica os resultados booleanos (d) e (e). x, y e z são objectos com identidades diferentes, mas y e z têm o mesmo valor.

P3

Suponha que tem duas listas de inteiros. Uma forma de determinar em que medida são diferentes consiste em calcular a soma das diferenças absolutas dos seus elementos. Uma fórmula simples será:





Aqui as listas são x e y, e têm n elementos. As barras verticais denotam o valor absoluto, isto é, correspondem à operação abs em Python.
Implemente o respectivo programa. Os argumentos são as duas listas de inteiros, como se mostra no exemplo da listagem abaixo.


>>> print distancia([1,2,3],[4,8,12])
18
>>>



Resposta

def d(x,y):
""" A distância entre duas listas de inteiros."""
soma = 0
for i in range(len(x)):
soma = soma + abs(x[i] - y[i])
return soma

Usando listas por compreensão:

def d_b(x,y):
return sum([ abs(x[i] - y[i]) for i in range(len(x))])

P4

Suponha que tem um ficheiro de texto onde guarda em cada linha o código da amostra de um produto, seguido do número de uma experiência feita com a amostra, seguido finalmente do valor da temperatura a que a experiência foi feita. A listagem abaixo mostra a parte inicial de um ficheiro tipo.


A 1 20
B 1 40
C 1 30
A 2 -10
C 2 5
B 2 15
B 3 60


Desenvolva um programa que leia esta informação e crie um dicionário em que as chaves são o código do produto e o valor a máxima temperatura a que a experiência foi feita. Admita que a temperatura pode variar entre -100 e 100 graus centígrados. A listagem abaixo mostra o resultado de executar o programa caso o ficheiro tivesse apenas os dados acima indicados.


>>> print dic_max_temp('/tempo/data/exame_r.txt')
{'A': 20, 'C': 30, 'B': 60}
>>>


Resposta

def dic_max_temp(ficheiro):
""" Do ficheiro ao dicionário."""
f_in = open(ficheiro, 'r')
dic = {}
linha = f_in.readline()

while linha != '':
cod,num,temp = linha[:-1].split()
dic[cod] = max(dic.get(cod,-100),int(temp))
linha = f_in.readline()
f_in.close()
return dic

P5

Usando o módulo cTurtle construa um programa que desenhe um tabuleiro quadrado semelhante ao da figura .






Por semelhante entende-se que pode ter outra dimensão. Suponha que tem ao seu dispor o programa que lhe permite desenhar um quadrado da cor que precisar, como se mostra na listagem abaixo.


def quad(tartaruga,lado,cor):
"""Desenha Quadrado da cor."""
tartaruga.begin_fill()
tartaruga.pen(shown=False, pencolor='black', fillcolor= cor)
for i in range(4):
tartaruga.fd(lado)
tartaruga.rt(90)
tartaruga.end_fill()




Resposta

def tabuleiro(tartaruga,n,lado):
tartaruga.pu()
tartaruga.goto(0,0)
tartaruga.pd()

for l in range(n):
# reposiciona no início da linha
tartaruga.setx(0)
tartaruga.sety(tartaruga.ycor() + lado)
# desenha linha de quadrados
for c in range(n):
if (c + l) % 2 == 0:
quad(tartaruga,lado,'white')
else:
quad(tartaruga,lado,'black')
tartaruga.setx(tartaruga.xcor() + lado)