sexta-feira, 30 de janeiro de 2015

Exame Recurso - Pergunta 1b

As respostas dadas pela generalidade dos(as) alunos(as) denota uma incompreensão de aspectos básicos e fundamentais de Python. Em Python tudo são objectos, e os objectos têm atributos. O nome é um dos atributos, e é através do nome que chegamos a outro valor, o valor. Assim, temos por exemplo:
>>> x = 5
>>> x
5
>>> a = ‘xpto’
>>> a
xpto
Neste exemplo, criámos objectos (5, ‘xpto’) e demos-lhes um nome (x, a).

Mas também se fizermos:
>>> def acme(n):
…  return 2*n
…
>>>
Criamos também um objecto, agora do tipo função, e estamos a dar-lhe o nome acme. Se agora perguntarmos pelo valor do objecto de nome acme o que recebemos de volta é o descritor da função:
>>> acme
<function acme at 0x102778488>
Então, no caso da pergunta do exame, quando fazemos:
a = funcao
Estamos a dizer que a é um outro nome para o objecto de tipo função, funcao. Logo, ao fazermos:
>>> print(a)
o que vai aparecer é a impressão do valor associado ao objecto de nome a, que é o mesmo do associado a funcao:
>>> def funcao():
…    print('128')
…
>>> a = funcao
>>> a
<function funcao at 0x102778510>
Notar que pode agora usar o nome a em vez de funcao e obter o mesmo resultado:
>>> funcao()
128
>>> a()
128

Exame Recurso - Pergunta 1a

Nesta pergunta estavam envolvidos vários conceitos: alcance das variáveis (local, global), passagem de parâmetros (real, formal) e mutabilidade. Na primeira situação, na chamada da função f1, a lista l (variável global, mutável) é usada como parâmetro real. Deste modo, no início da execução da função, l e lista são nomes de objectos que têm a mesma identidade. No entanto, na primeira instrução de f1 a atribuição lista = [1,2,3] faz com que os nomes ficam associados a objectos diferentes. Deste modo, todas as alterações a lista não afectam l, pelo que na linha 12 vai aparecer o valor (inalterado) de l = [1,2].

Na segunda situação, ao contrário da anterior, não é desfeita a ligação dos nomes l e lista ao mesmo objecto. Tratando-se de um objecto mutável, todas alterações feitas ao parâmetro formal serão reflectidas no parâmetro real, pelo que ao acrescentarmos 3 à lista usando o método append, essa alteração vai também afectar l. Logo, na linha 17, vai aparecer [1,2,3].

quinta-feira, 8 de janeiro de 2015

Teste Final - Pergunta 4

Esta pergunta envolvia manipular um ficheiro em que cada linha é formada por um nome seguido de um ou mais números inteiros. O objectivo é criar um novo ficheiro, deixando o antigo inalterado, adicionando no final de cada linha a soma dos números dessa linha. Valorizava-se uma solução em que as linhas no ficheiro eram reordenadas por ordem crescente das somas.

Solução:

A solução apresentada inclui o ordenamento das linhas. Para isso usamos o método itemgetter do módulo operator. Começamos por percorrer o ficheiro linha a linha, calculando a soma dos números e guardando o resultado numa lista. É feito o ordenamento da lista de seguida. Na terceira parte, criamos o novo ficheiro e guardamos a informação.
from operator import itemgetter

def trata_ficheiro(ficheiro,novo_ficheiro):
    with open(ficheiro,'r') as f_in:
        novos_dados = list()
        for linha in f_in:
            lista_linha = linha[:-1].split()
            valor = sum([int(elem) for elem in lista_linha[1:]])
            lista_linha.append(valor)
            novos_dados.append(lista_linha)
    
    novos_dados.sort(key=itemgetter(-1))
    with open(novo_ficheiro,'w') as f_out:
        for linha in novos_dados:
            linha[-1] = str(linha[-1]) + '\n'
            cadeia = ' '.join(linha)
            f_out.write(cadeia)
            
    return novos_dados

Teste Final - Pergunta 3

Guardamos num dicionário as classificações que diversos filmes receberam dos críticos (um valor entre 1 e 5). Queremos manter o dicionário actualizado e saber qual o filme com melhor pontuação média. Só podem ser candidatos a melhor filme os que tiverem pelo menos 3 valores.

Solução:

A solução abaixo usa o setdefault para actualizar o dicionário das classificações. Deste modo fica resolvida a questão de incluir uma primeira classificação ou simplesmente actualizar. Actualizado o dicionário, ele é percorrido, item a item, para determinar o melhor, prevendo o caso de não ter as 3 classificações.
def melhor_filme(filmes, classific):
    # actualiza
    nome = classific[0]
    valores = classific[1:]
    filmes[nome] = filmes.setdefault(nome,[]) + valores
    # calcula melhor
    m_filme = ''
    m_pont = 0
    for nome,pontos in filmes.items():
        comp = len(pontos)
        if comp < 3:
            continue
        media = (sum(pontos)/comp)
        if media > m_pont:
            m_filme = nome
            m_pont = media
    return (m_filme, m_pont)

Teste Final - Pergunta 2

Simular um jogo em que se atiram dardos a um alvo. No caso do teste o problema era mais simples pois não se pretendia simular o jogo mas apenas desenhar o alvo e simular o lançamento de um dardo. E fazer isto usando o módulo turtle. E construindo o programa de forma modular.

Vamos então dividir a solução em duas partes: uma para desenhar o alvo, e outra para simular o lançamento do dardo. Por outro lado, vamos fazer com que o desenho do alvo seja feito à custa de uma mesma função, a que desenha um círculo de uma dada cor, numa dada posição e com o dado raio. Essa função chama-se circunf. A partir dela desenhar o alvo é fácil (função alvo). Estas última precisa saber apenas o centro do alvo e o valor dos raios. Os cuidados a ter são desenhar as circunferências da maior para a mais pequena (notar o ordenamento inverso da lista dos raios), e controlar a alternância das cores.

O lançamento do dardo recorre ao módulo random para geral a posição, e ao comando dot para marcar a localização do dardo. Neste último caso, não conhecendo o comando dot, podia socorrer-se uma vez mais ao desenho de uma circunferência…
import turtle

def circunf(posx, posy, raio, cor):
    # posiciona
    turtle.pu()
    turtle.goto(posx,posy-raio)
    turtle.pd()
    # define cor e desenha
    turtle.fillcolor(cor)
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
    
def alvo(posx, posy,raios):
    """
    raios: lista dos raios dos discos
    """
    raios.sort(reverse=True)
    cor = 'black'
    for raio in raios:
        circunf(posx,posy,raio,cor)
        if cor == 'black':
            cor = 'white'
        else:
            cor = 'black'

            
def dardo():
    import random
    posx = random.randint(-100,100)
    posy = random.randint(-100,100)
    turtle.pu()
    turtle.goto(posx,posy)
    turtle.pd()
    turtle.dot(8,'red')
    turtle.hideturtle()


if __name__ == ‘__main__’:
    alvo(0,0,[20,40,60,80])
    dardo()
    turtle.exitonclick()
Como se pode ver pelo código o programa é facilmente generalizável. Alvo de vários tamanhos (directo) e cores (passando as cores para argumentos), por exemplo.

Teste Final - Pergunta 1a

Pedia-se para dizer o que eram e para que serviam as excepções e as asserções. Por outro lado, também se pretendia saber as suas semelhanças e as suas diferenças, e ainda a apresentação de um exemplo concreto de cada.

De um modo geral excepções e asserções são formas de controlar possíveis erros ou verificar condições num programa.

As excepções são instruções de controlo, enquanto as asserções são instruções simples. As excepções permitem que o programa continue a ser executado, mesmo na presença de um erro, pois prevê acções de remediarão. No caso das asserções isso não acontece, pois o programa termina no caso da asserção não se verificar. Assim estas últimas são sobretudo usadas para depurar os programas.

As excepções são activadas (raise) quando durante a execução se verifica um erro. Os erros podem ser de diversos tipos, podendo as excepções ser genéricas ou dirigidas a um ou mais tipos de erro. Um exemplo simples e clássico é prevenir erros nas operações:
def divisao_protegida(x,y):
 try:
  res = x/y
  return res
 except ZeroDivisionError:
  print(‘ERRO: divisão por zero’)
No caso das asserções elas permitem verificar condições para que um programa possa correr. Um exemplo, também clássico, permite evitar um ciclo infinito:
def factorial(x):
 assert (x >=0), ‘ERRO: número negativo’
 res = 1
 while x:
  res *= x
  x -= 1
 return res

Teste Final - Pergunta 1b

A pergunta 1b do teste pedia para dizer, justificadamente, o que aparecia no lugar dos pontos de interrogação na sessão no interpretador seguinte:
>>> def xpto(x):
…    x += 1
…   print(x)
…   return
…
>>> x = 7
>>> print(xpto(x))
>>> ??? # —> 8
>>> ??? # —> None
>>> x
>>> ??? # —> 7
>>>
À frente dos pontos de interrogação colocámos o resultado real. Das respostas fica a certeza de que ainda existe muita confusão conceptual.

Vamos por partes.

Na sessão começamos por definir a função xpto. A função tem um parâmetro formal de nome x. O corpo da função consiste no incremento de uma unidade do parâmetro formal, seguido pela impressão do seu valor, terminando pela instrução de return, neste caso sem expressão associada.

De seguida associamos o nome x ao objecto 7.

Mandamos depois executar a função xpto passando como parâmetro real o nome x, pedindo ainda para imprimir o resultado da execução.

No final, queremos saber qual o valor associado ao nome x.

Que conceitos estão em jogo?

Desde logo o nome x fora da definição é uma variável do ambiente global, enquanto o parâmetro formal, com o mesmo nome x, é uma variável local à definição. Por outro lado, ao ser associado ao objecto 7, o nome x corresponde a um objecto imutável.

O que acontece quando mandamos executar a função xpto? É feita a passagem de parâmetros, que em Python significa que a identidade do x global (parâmetro real) é passada para o x local (parâmetro formal). Repito: a identidade! De seguida é executada a instrução x += 1. Como se trata de um objecto imutável as identidades do x global e do x local ficam diferentes. Os dois objectos, embora com o mesmo nome, estão guardados em zonas diferentes do espaço dos objectos!

Da execução de xpto resulta a instrução print(x) que vai imprimir o novo valor de x (local), ou seja 8.

Por outro lado, todas as funções devolvem sempre um resultado. Caso não exista return, ou a instrução de return não tenha uma expressão associada, o valor devolvido pela função é sempre o mesmo, o objecto None. Esse objecto é passado à instrução print que faz o que lhe compete, imprimindo … None.

Depois de executada a função xpto os nomes locais à definição (incluindo os parâmetros formais) deixam de estar associados a objectos. Independentemente disso, como já existia separação entre os objectos quando se tenta saber o valor do x “externo” o seu valor continua a ser o anteriormente definido, ou seja, 7. Isto tudo porque o objecto associado é imutável!!!

Portanto, a passagem de parâmetros não se faz por cópia, mas sim por passagem da identidade (referência) do parâmetro real ao parâmetro formal. Por outro lado não é o facto de uma variável ser global e outra local que explica o que aconteceu

Entendeu? Para tirar dúvidas veja o que acontece na situação seguinte:
>>> def xpto2(x):
…    x.append('a')
…    print(x)
…
>>> x = [1,2,3]
>>> print(xpto2(x))
[1, 2, 3, 'a']
None
>>> x
[1, 2, 3, ‘a']