Fundamentos da Programação Solução do Exame 1 de Fevereiro de 2017 09:00 11:00 1. Usando palavras suas e, no máximo, em três linhas explique os seguintes conceitos. Explicações dadas através de exemplos serão classificadas com zero valores. (a) (0.5) Abstração. Consiste em ignorar certos aspectos de uma entidade, considerando apenas os aspectos relevantes. (b) (0.5) Abstracção procedimental. Consiste em considerar o que um programa faz e ignorar o modo como o faz. (c) (0.5) Abstracção de dados. Consiste em considerar as propriedades de um tipo de dados, ignorando o modo como este é representado. 2. (1.0) Escreva uma função com o nome cinco que tem o valor True se o seu argumento for 5 e False no caso contrário. Não pode utilizar uma instrução if. def cinco(x): return x == 5 3. (1.0) Um número inteiro, n, diz-se triangular se existir um inteiro m tal que n = 1+2+...+(m 1)+m. Escreva uma função chamada triangular que recebe um número inteiro positivo n, e cujo valor é True apenas se o número for triangular. No caso de n ser 0 deverá devolver False. Por exemplo, >>> triangular(6) True >>> triangular(8) False
Número: Pág. 2 de 9 def triangular (n): soma = 0 for i in range(1,n): soma = soma + i if soma == n: return True return False 4. Considere a seguinte gramática em notação BNF, cujo símbolo inicial é S : hsi ::= hai hbi hai ::= hxi hxi hai hbi ::= hyi hyi hbi hxi ::= A B C D hyi ::= 1 2 3 4 (a) (0.5) Diga quais são os símbolos terminais e quais são os símbolos não terminais da gramática. Símbolos terminais: A, B, C, D, 1, 2, 3, 4 Símbolos não terminais: hsi, hai, hbi, hxi, hyi (b) (0.5) Descreva informalmente as frases que pertencem à linguagem. As frases da linguagem começam com um número arbitrários das letras A, B, C, D, por qualquer ordem, mas pelo menos uma delas, seguidas por um número arbritário dos números 1, 2, 3, 4, por qualquer ordem, mas pelo menos um deles. (c) (1.5) Escreva a função reconhece que recebe uma cadeia de caracteres e devolve True se o seu argumento corresponde a uma frase gerada pela gramática e False em caso contrário. A sua função deve verificar a correcção do argumento. def reconhece(frase): if not isinstance(frase, str): return False i = 0 comp = len(frase) while i < len(frase) and frase[i] in [ A, B, C, D ]: i = i + 1 if i == 0 or i == comp: return False for i in range(i, comp): if frase[i] not in [ 1, 2, 3, 4 ]: return False return True
Número: Pág. 3 de 9 5. (1.5) A congruência de Zeller é um algoritmo inventado pelo matemático alemão Julius Christian Zeller (1822 1899) para calcular o dia da semana para qualquer dia do calendário. Para o nosso calendário, o calendário Gregoriano, a congruência de Zeller é dada por: h = q + 13(m + 1) + K + K + J 5 4 4 2J mod 7 em que h é o dia da semana (0 = Sábado, 1 = Domingo,...), q é o dia do mês, m é o mês (3 = março, 4 = abril,..., 14 = fevereiro) os meses de janeiro e fevereiro são contados comos os meses 13 e 14 do ano anterior, K é o ano do século (ano mod 100), J é o século (bano/100c). Esta expressão utiliza a função matemática, chão, denotada por bxc, a qual converte um número real x no maior número inteiro menor ou igual a x. A definição formal desta função é bxc = max {m 2 Z m apple x}. A expressão utiliza também a função módulo, em que a mod b representa o resto da divisão de a por b. Escreva uma função em Python, chamada dia_da_semana, que recebe três inteiros correspondentes a um dia, um mês e um ano e que devolve o dia da semana em que calha essa data. A sua função deve utilizar outras funções auxiliares a definir por si. Por exemplo, >>> dia_da_semana(1, 2, 2017) quarta def dia_da_semana(dia, mes, ano): if mes == 1 or mes == 2: mes = mes + 12 ano = ano - 1 K = ano % 100 J = ano//100 return converte((dia + 13*(mes+1)//5 + K + \ K//4 + J//4-2*J) % 7) def converte(d): dias = [ sabado, domingo, segunda, terca, quarta, quinta, sexta ] return dias[d] 6. (1.0) Escreva a função maior_elemento que recebe um tuplo contendo números inteiros, e devolve o maior elemento do tuplo. Por exemplo, >>> maior_elemento((2, 4, 23, 76, 3, -12)) 76 def maior_elemento(t): maior = t[0] for e in t: if e > maior: maior = e return maior
Número: Pág. 4 de 9 7. (1.0) Escreva a função e remove_repetidos que recebe uma lista e devolve a lista obtida da lista original em que todos os elementos repetidos foram removidos. Por exemplo, >>> remove_repetidos([2, 4, 3, 2, 2, 2, 3]) [2, 4, 3] >>> remove_repetidos([2, 5, 7]) [2, 5, 7] def remove_repetidos(lst): for i in range(len(lst)-1, 1, -1): if lst[i] in lst[0:i-1]: del(lst[i]) return lst 8. (1.0) Escreva a função recursiva com operações adiadas, soma_els_atomicos, que recebe como argumento um tuplo, cujos elementos podem ser outros tuplos, e que devolve a soma dos elementos correspondentes a tipos elementares de dados que existem no tuplo original. Não é necessário verificar os dados de entrada. Por exemplo, >>> soma_els_atomicos((3, ((((((6, (7, ))), ), ), ), ), 2, 1)) 19 >>> soma_els_atomicos(((((),),),)) 0 def soma_els_atomicos(t): if t == (): return 0 elif isinstance(t[0], tuple): return soma_els_atomicos(t[0]) + soma_els_atomicos(t[1:]) return t[0] + soma_els_atomicos(t[1:]) 9. (1.0) Escreva a função recursiva de cauda inverte que recebe uma lista contendo inteiros e devolve a lista com os mesmos elementos mas por ordem inversa. def inverte(lst): def inverte_aux(lst, res): if lst == []: return res return inverte_aux (lst[1:], [lst[0]] + res) return inverte_aux(lst, []) 10. Considere a seguinte função:
Número: Pág. 5 de 9 def misterio(x, n): if n == 0: return 0 return x * n + misterio(x, n - 1) (a) (0.5) Explique o que é calculado pela função misterio, de preferência, usando uma expressão matemática. nx x i i=1 (b) (0.5) Mostre a evolução do processo gerado pela avaliação de misterio(2, 3). misterio(2, 3) 6 + misterio(2, 2) 4 + misterio(2, 1) 2 + misterio(2, 0) 0 2 6 12 12 (c) (0.5) De que tipo é o processo gerado pela função apresentada? Justifique. Processo recursivo linear pois existe uma fase de expansão e uma fase contracção e o número de chamadas à função varia linearmente com o segundo argumento da função. 11. (1.0) Escreva a função maior_inteiro que recebe um inteiro positivo e devolve o maior inteiro tal que 1+2+...+ n apple limite sendo o valor de limite o argumento da sua função. Por exemplo: >>> maior_inteiro(6) 3 >>> maior_inteiro(12) 4 def maior_inteiro(limite): soma = 0 i = 0 while soma <= limite: i = i + 1 soma = soma + i return i - 1
Número: Pág. 6 de 9 12. (1.0) Utilizando os funcionais sobre listas (filtra, transforma, acumula) que considere necessários, escreva a função conta_pares, que recebe uma lista de inteiros, e devolve o número de elementos pares da lista. A sua função deve conter apenas uma instrução, a instrução return. Por exemplo: >>> conta_pares([1, 2, 3, 4, 5, 6]) 3 def conta_pares(lst): return len(filtra(lambda x: x % 2== 0, lst)) 13. (1.0) Escreva uma função em Python que recebe um dicionário cujos valores associados às chaves correspondem a listas de inteiros e que devolve o dicionário que se obtém invertendo o dicionário recebido, no qual as chaves são os inteiros que correspondem aos valores do dicionário original e os valores são as chaves do dicionário original às quais os valores estão associados. Por exemplo, >>> inverte_dic({ a : [1, 2], b : [1, 5], c : [9], d : [4]}) {1: [ a, b ], 2: [ a ], 4: [ d ], 5: [ b ], 9: [ c ]} def inverte_dic(d): res = {} for e in d: for v in d[e]: if v in res: res[v] = res[v] + [e] res[v] = [e] return res 14. (1.0) Os automóveis mais recentes mostram a distância que é possível percorrer até ser necessário um reabastecimento. Pretende-se criar esta funcionalidade em Python através da classe automovel. Esta classe é construída indicando a capacidade do depósito, a quantidade de combustível no depósito e o consumo do automóvel em litros aos 100 km. A classe automovel apresenta os seguintes métodos: combustivel devolve a quantidade de combustível no depósito; autonomia devolve o numero de Km que é possível percorrer com o combustível no depósito; abastece(n_litros) aumenta em n_litros o combustível no depósito. Se este abastecimento exceder a capacidade do depósito, gera um erro e não aumenta a quantidade de combustível no depósito; percorre(n_km) percorre n_km Km, desde que a quantidade de combustível no depósito o permita, em caso contrário gera um erro e o trajecto não é efectuado.
Número: Pág. 7 de 9 Por exemplo: >>> a1 = automovel(60, 10, 15) >>> a1.autonomia() 66 >>> a1.abastece(45) 366 Km até abastecimento >>> a1.percorre(150) 216 Km até abastecimento >>> a1.percorre(250) ValueError: Não tem autonomia para esta viagem class automovel: def init (self, cap_dep, quant_comb, consumo): self.cap_dep = cap_dep self.quant_comb = quant_comb self.consumo = consumo def combustivel(self): return self.quant_comb def autonomia(self): return round(100*self.quant_comb//self.consumo) def abastece(self, litros): if self.quant_comb + litros > self.cap_dep: raise ValueError ( Capacidade do depósito excedida ) self.quant_comb = self.quant_comb + litros return str(round(100*self.quant_comb//self.consumo)) + \ Km até abastecimento def percorre(self, km): comb_gasto = km * self.consumo / 100 if self.quant_comb - comb_gasto < 0: raise ValueError ( Não tem autonomia para esta viagem ) self.quant_comb = self.quant_comb - comb_gasto return str(round(100*self.quant_comb//self.consumo)) + \ Km até abstecimento 15. Considere o tipo abstrato carta, correspondente a uma carta de jogar, o qual é caraterizado por um naipe (espadas, copas, ouros e paus) e por um valor (A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K). A representação externa de uma carta corresponde a uma cadeia de caracteres contendo o naipe e o valor. Por exemplo, E10 éa representação externa do 10 de espadas. O tipo carta tem as seguintes operações básicas: Construtor: cria_carta : {E,C,O,P} {A, 2, 3, 4, 5, 6, 7, 8, 9, 10,J,Q,K} 7! carta cria_carta(n, v) tem como valor uma carta com naipe n e valor v.
Número: Pág. 8 de 9 Seletores: naipe : carta 7! {E,C,O,P} naipe(c) tem como valor naipe da carta c. valor : carta 7! {A, 2, 3, 4, 5, 6, 7, 8, 9, 10,J,Q,K} valor(c) tem como valor o valor da carta c. Reconhecedor: e_carta : universal 7! booleano e_carta(x) tem o valor verdadeiro, se x é uma carta e tem o valor falso, em caso contrário. (a) (1.0) Escolha uma representação para o tipo carta e, com base nessa representação, escreva as operações básica para este tipo, incluindo o transformador de saída. Uma carta será representada por um tuplo em que o primeiro elemento corresponde ao naipe (um elemento da lista [ E, C, O, P ]) e o segundo elemento corresponde ao valor (um elemento da lista [ A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K ]). def cria_carta(n, v): if n in ( E, C, O, P ) and \ v in ( A, 2, 3, 4, 5, 6, 7, 8, \ 9, 10, J, Q, K ): return (n, v) raise ValueError ( cria_carta: argumentos errados ) def naipe(c): return c[0] def valor(c): return c[1] def e_carta(x): return isinstance(x, tuple) and (len(x) == 2) and \ x[0] in ( E, C, O, P ) \ and x[1] in ( A, 2, 3, 4, 5, 6, 7, 8, 9, \ 10, J, Q, K ) def rep_carta(c): return c[0] + c[1] (b) (1.0) Usando o tipo carta, defina uma função que devolve uma lista em que cada elemento corresponde a uma carta de um baralho. def baralho(): b = [] for n in ( E, C, O, P ): for v in ( A, 2, 3, 4, 5, 6, 7, 8, \ 9, 10, J, Q, K ): b = b + [cria_carta(n, v)] return b
Número: Pág. 9 de 9 (c) (1.0) Usando o tipo carta e recorrendo à função random(), a qual produz um número aleatório no intervalo [0, 1[, escreva uma função, baralha, que recebe uma lista correspondente a um baralho de cartas e baralha aleatoriamente essas cartas, devolvendo a lista que corresponde às cartas baralhadas. SUGESTÃO: percorra sucessivamente as cartas do baralho trocando cada uma delas por uma outra carta selecionada aleatoriamente. Mostre as cartas utilizando a representação externa. Por exemplo >>> baralha(baralho()) [ O8, E3, CA, PA, E8, PQ, CJ, O2, O9, EJ, E2, C8, OJ, PJ, C2, O10,... ] def baralha(b): from random import random for i in range(len(b)): ni = int(random() * len(b)) b[i], b[ni] = b[ni], b[i] return transforma(rep_carta, b) def transforma(fn, lst): if lst == []: return lst return [fn(lst[0])] + transforma(fn, lst[1:])