sexta-feira, 15 de dezembro de 2017

Teste # 2 - Especial

P2

Suponha que tem uma palavra e pretende criar uma outra de maneira que os caracteres da palavra inicial nas posições pares passam para as posições ímpares na nova palavra, e os caracteres das posições ímpares da palavra inicial passam para as posições pares da nova palavra. Por exemplo: \\
>>> palavra = 'a1b2c3d'
>>> print(troca(palavra))
1a2b3cd
Implemente o respectivo programa.

A solução adoptada começa por dividir a palavra nos seus elementos pares e nos ímpares. Depois, num ciclo, constrói-se a nova palavra. Finalmente, verifica-se o caso do tamanho ser ímpar o que o briga a acrescentar o último elemento na posição par, que não foi introduzido no ciclo.
def troca(pal):
    """Os caracteres nas posições pares passam para ímpares e os nas posições ímpares passam para as posiçõesp pares."""
    pares = pal[::2]
    impares = pal[1::2]
    nova_pal = ''
    for i in range(len(pal)//2):
        nova_pal += impares[i] + pares[i]
    if len(pal)%2 != 0:
        nova_pal += pares[-1]
    return nova_pal
P3

Suponha que tem uma imagens a preto e branco e que pretende limpar uma parte da imagem, ou seja, colocar os pixeis dessa parte todos a branco. A zona a limpar é dada por dois pontos, o canto superior esquerdo e o canto superior direito. Por exemplo:\\
>>> img = [[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]]
>>> sup = (1,2)
>>> inf = (3,4)
>>> print(limpar(img,sup,inf))
[[1, 1, 1, 1, 1], [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [1, 1, 0, 0, 0]]
Implemente o respectivo programa.

A solução apresentada percorre todas a matriz de modo usual, por linhas e para cada linha por colunas, limitando as coordenadas ai interior do rectângulo definido pelo canto inferior esquerdo e santo superior direito, muda os piteis para zero (branco). Antes de proceder à alteração, verifica se a operação é possível.
def limpar(imagem, sup_esq,inf_dir):
    """ Limpa a imagem entre o canto superior esaquerdo e o canto superior direito."""
    # verifica se é possível
    num_linhas = len(imagem)
    num_colunas = len(imagem[0])
    possivel_linhas = (sup_esq[0] <= inf_dir[0] <= num_linhas )
    possivel_colunas = (sup_esq[1] <= inf_dir[1] <= num_colunas) 
    if possivel_linhas and possivel_colunas: 
        for linha in range(sup_esq[0], inf_dir[0]+1):
            for coluna in range(sup_esq[1],inf_dir[1]+1):
                imagem[linha][coluna] = 0
    return imagem

Teste 3 - TP2

P2

Suponha que tem uma árvore genealógica organizada usando um dicionário. A chave é um tuplo com dois elementos, em que cada elemento é um tuplo com o nome do progenitor e respectiva idade. O valor é uma lista com tuplos, em que cada tuplo tem o nome do descendente e respectiva idade. Um exemplo:
ag = {(('ernesto',64),('anabela',44)): [('daniela', 15)], (('vitor',72),('irm',68)): [('ines', 39)], (('carlos',68),('mena',65)): [('ricardo', 42), ('marcos', 37)],(('josé',85),('lurdes',82)): [('ernesto', 64), ('afonso',74),('vitor',72), ('carlos',68),('isabel',66)], (('joaobranco',70),('isabel',66)): [('anaisabel', 35),('joana',29)], (('jbp',77),('jbm',70)): [('joaobranco', 70), ('graça',68)]}
Escreva um programa que dada uma árvore genealógica e o nome de uma pessoa, devolva o nome do sobrinho(a) mais velho(a). Exemplo:
>>> sob_mais_velho(ag,'afonso')
ricardo
Os meus sobrinhos são os filhos dos meus irmãos. Daí fazer sentido ter definições auxiliares para determinar os filhos e os irmãos de uma pessoa.
def irmaos(ag, pessoa):
    for prog,filhos in ag.items():
        for filho in filhos:
            if pessoa == filho[0]:
                filhos.remove(filho)
                return filhos
            
def meus_filhos(ag, pessoa):
    for prog, filhos in ag.items():
        if (pessoa == prog[0][0]) or (pessoa == prog[1][0]):
            return filhos
    return None
Resolvida esta questão, o resto é fácil. Começamos por calcular os irmãos da pessoa. Caso existam, calculamos os filhos de cada um dos irmãos. Finalmente, determinamos qual deles é o mais velho.
def sob_mais_velho(ag,pessoa):
    irm = irmaos(ag,pessoa)
    if irm:
        filhos = []
        for irmao in irm:
            filhos.extend(meus_filhos(ag,irmao[0]))
        velho = filhos[0]
        for fil in filhos[1:]:
            if velho[1] < fil[1]:
                velho = fil
        return velho[0]
    return ()
P3

Suponha que tem dois ficheiros de texto com exactamente o mesmo número de linhas. Pretende-se que escreva um programa que dados esses dois ficheiros crie um terceiro cujo conteúdo resulta da junção das linhas dos dois primeiros, feita do seguinte modo. Se A e B forem os dois ficheiros de origem e C o ficheiro resultado, então C vai ter na primeira linha o conteúdo da primeira linha do ficheiro A seguido do conteúdo da primeira linha do ficheiro B, na segunda linha o conteúdo da segunda linha do ficheiro A seguido do conteúdo da segunda linha do ficheiro B, e assim sucessivamente.

Solução trivial. Lemos os dois ficheiros e guardamos o seu conteúdo como listas de linhas. De seguida criamos uma nova lista juntando as linhas de mesma ordem. Finalmente, criamos o novo ficheiro. Notar que temos que retirar o indicador de mudança de linha das linhas do primeiro ficheiro. Usamos o tab como separador.
def concatena_ficheiros(ficheiro_1,ficheiro_2, ficheiro_3):
    with open(ficheiro_1, 'r', encoding='utf-8') as fich_1:
        with open(ficheiro_2,'r',encoding='utf-8') as fich_2:
            with open(ficheiro_3, 'w',encoding='utf-8') as fich_3:
                dados_1 = fich_1.readlines()
                dados_2 = fich_2.readlines()
                dados_3 = []
                for i,linha in enumerate(dados_1):
                    dados_3 += [linha[:-1] +  '\t' + dados_2[i]]
                fich_3.writelines(dados_3)

Teste 3 - TP1

P2

Suponha que tem uma árvore genealógica organizada usando um dicionário. A chave é um tuplo com dois elementos, em que cada elemento é um tuplo com o nome do progenitor e respectiva idade. O valor é uma lista com tuplos, em que cada tuplo tem o nome do descendente e respectiva idade. Um exemplo:
ag = {(('ernesto',64),('anabela',44)): [('daniela', 15)], (('vitor',72),('irm',68)): [('ines', 39)], (('carlos',68),('mena',65)): [('ricardo', 42), ('marcos', 37)],(('josé',85),('lurdes',82)): [('ernesto', 64), ('afonso',74),('vitor',72), ('carlos',68),('isabel',66)], (('joaobranco',70),('isabel',66)): [('anaisabel', 35),('joana',29)], (('jbp',77),('jbm',70)): [('joaobranco', 70), ('graça',68)]}
Escreva um programa que dada uma árvore genealógica e o nome de uma pessoa, devolva o nome do mais velho dos avós. Exemplo:
>>> avo_mais_velho(ag,'anaisabel')
josé
Os avós de uma pessoa são os pais dos pais. Daí que a solução que vamos apresentar use uma definição auxiliar para determinar quem são os pais de alguém. Relativamente ao que tinha sido feito nas aulas apenas se teve que ter em linha de conta que agora cada pessoa tem associada a respectiva idade.

def pais_de(ag,pessoa):
    for ch,val in ag.items():
        for nome, idade in val:
            if nome == pessoa:
                return ch
    return ()
Usando esta função auxiliar, o resto é simples: determinamos quem são os pais da pessoa. se existirem, achamos os pais da cada um dos progenitores e chegamos aos quatro avós. De seguida obtemos o mais velho.
def avo_mais_velho(ag,pessoa):
    pais = pais_de(ag,pessoa)
    if pais:
        avos = []
        for progenit in pais:
            avos.extend(pais_de(ag,progenit[0]))
        # mais velho
        velho = avos[0]
        for av in avos[1:]:
            if velho[1] < av[1]:
                velho = av
        return velho[0]
    return None
P3

Suponha que tem dois ficheiros de texto com exactamente o mesmo número de linhas. Pretende-se que escreva um programa que dados esses dois ficheiros crie um terceiro cujo conteúdo resulta da fusão dos dois primeiros. A fusão é feita alternando as linhas dos dois ficheiros de origem. Assim, se A e B forem os dois ficheiros de origem e C o ficheiro resultado, então C vai ter na primeira linha a primeira linha do ficheiro A, na segunda linha a primeira linha do ficheiro B, na terceira linha a segunda linha do ficheiro A, na quarta linha a segunda linha do ficheiro B, e assim sucessivamente.

Problema muito simples. Lemos os ficheiros de entrada como listas de linhas (readlines). De seguida criamos uma nova lista em que os elementos das listas de entrada são colocadas na posição certa. Finalmente, escrevemos a lista de linhas no novo ficheiro. Como diz o enunciado, esta solução supõe o mesmo número del linhas nos dois ficheiros de entrada.
def junta_ficheiros_b(ficheiro_1,ficheiro_2, ficheiro_3):
    with open(ficheiro_1, 'r', encoding='utf-8') as fich_1:
        with open(ficheiro_2,'r',encoding='utf-8') as fich_2:
            with open(ficheiro_3, 'w',encoding='utf-8') as fich_3:
                dados_1 = fich_1.readlines()
                dados_2 = fich_2.readlines()
                dados_3 = []
                for i in range(len(dados_1)):
                    dados_3.extend([dados_1[i],dados_2[i]])
                fich_3.writelines(dados_3))

quinta-feira, 14 de dezembro de 2017

BINGO!

Se pensar primeiro é fundamental em programação, também sabemos que conhecer bem a linguagem em que vamos expressar a solução é igualmente importante. Numa das fichas recente de exercícios vinha a seguinte questão:

Os cartões para jogar BINGO têm 5 colunas, cada uma com 5 números. As colunas têm associado as letras B, para a primeira coluna, I para a segunda, N para a terceira, G para a quarta e O para a quinta. Nas primeira colunas os números podem ser entre 1 e 15, na segunda entre 16 e 30, na terceira entre 31 e 45, na quarta entre 46 e 60 e na quinta entre 61 e 75. Escreva um programa que permita gerar e guardar de modo apropriado um cartão de bingo. Escreva um segundo programa que permita visualizar um cartão de bingo. Vejamos a solução para a primeira questão:
import random

def bingo():
    nome = 'BINGO'
    cartao = dict()
    for i,letra in enumerate(nome):
        lista = list(range(i*15 +1, i*15 + 16))
        numeros = random.sample(lista,5)
        numeros.sort()
        cartao[letra] = numeros
    return cartao
O que tem de especial a solução? Desde logo usamos um dicionário para guardar a representação de um cartão. A chave é uma das letras de ‘BINGO’ e o valor são os 5 números. Usamos o método sample do módulo random para gerar os 5 números. Este método garante que os números serão todos diferentes. Finalmente usamos o método enumerate para poder ter uma forma simples de gerar os diferentes intervalos dos números.

A segunda questão era a da visualização do cartão. Aqui vai a solução:
def mostra_bingo(cartao):
    numeros_colunas = list(cartao.values())
    numeros_linhas = list(zip(*numeros_colunas))
    print('%2s\t%2s\t%2s\t%2s\t%2s\t'% tuple('BINGO'))
    print('_' * 35)
    for linha in numeros_linhas:
        print('%2s\t%s\t%s\t%s\t%s\t'% linha)

Qual era a dificuldade deste problema? O facto de termos de passar de listas de números associados a letras para uma visualização por colunas. Esta é uma situação onde a operação zip mostra todo o seu esplendor. O facto de fazermos:
numeros_linhas = list(zip(*numeros_colunas))
tem a seguinte explicação. Primeiro, zip é um iterador pelo que para obtermos todos os seus elementos de uma vez temos que usar list sobre o resultado de zip. Depois, o asterisco (*) resulta de zip se aplicar a uma sequência de argumentos e numeros_colunas ter apenas um. numeros_colunas é uma lista em que cada elemento é uma lista de 5 listas cada uma com 5 números. Com o asterisco passamos a ter as 5 listas como argumentos do zip.

 B  I  N  G  O 
__________________
 2 17 33 49 61 
 5 18 34 50 62 
 8 26 35 56 66 
11 27 37 57 72 
13 28 40 60 74

Pensar primeiro…

Programamos para resolver problemas, pelo que o centro da nossa atenção deve ser o problema. Por isso a primeira coisa que devemos fazer é pensar. Pensar para perceber o enunciado: qual é a entrada, qual é a saída. Pensar para definir uma estratégia: posso decompor o problema em sub-problemas mais simples? Já resolvi algum problema semelhante?

Este intróito vem a propósito da dificuldade que alguns encontraram em resolver o problema seguinte: Suponha que tem um dicionário que relaciona receitas com ingredientes. Faça um programa que retorne outro dicionário contendo os ingredientes usados no maior número de receitas, bem como as receitas em que cada um é usado. Por exemplo:

>>> receitas={’sonhos’:[’agua’,’farinha’,’manteiga’, ’ovos’,’acucar’],’rabanadas’:[’pao’,’leite’,’ovos ’,’manteiga’,’acucar’],’leite creme’:[’acucar’,’ farinha’,’ovos’,’leite’]} 
>>> ingredientes_mais_usados(receitas)
{’ovos’: [’sonhos’, ’rabanadas’, ’leite creme’], ’ 
acucar’: [’sonhos’, ’rabanadas’, ’leite creme’]} 
Se pensarmos um pouco verificamos que temos que passar de um dicionário em que as chaves são nomes de receitas e os valores são listas de produtos, para um dicionário em que as chaves são produtos e os valores listas de receitas onde esses produtos aparecem. Ou seja: temos que inverter o dicionário. Este é um problema que já resolvemos no passado:
def meu_inverte_dicio(dicio):
    novo_dicio = dict()
    for ch, val in dicio.items():
        for  ingrediente in val:
            novo_dicio[ingrediente] = novo_dicio.setdefault(ingrediente,[]) + [ch]
    return novo_dicio
Esta solução mostra a grande vantagem em usar o método setdefault!!

A partir do momento que temos o dicionário invertido temos que o percorrer à procura dos elementos cujo valor tem tamanho máximo. Já sabemos como resolver o problema semelhante de encontrar o elemento que é o “maior” de acordo com um dado critério, quando os elementos estão guardados numa lista. Neste caso, o problema é mais complexo por os elementos estarem num dicionário e por querermos não um mas todos os que têm dimensão máxima. Uma solução, possível passa por construir primeiro uma lista e só depois passar a lista a dicionário.

def meu_ingredientes_mais_usados(dicio):
    dicio_ingredientes = meu_inverte_dicio(dicio)
    lista_resultado = []
    tamanho = 0
    for ch, val in dicio_ingredientes.items():
        if len(val) > tamanho:
            lista_resultado = [(ch,val)]
            tamanho = len(val)
        elif len(val) == tamanho:
            lista_resultado.append((ch,val))
    dicio_resultado = dict(lista_resultado)
    return dicio_resultado
Mas nada impede que se construa o dicionário final directamente:
def meu_ingredientes_mais_usados_b(dicio):
    dicio_ingredientes = meu_inverte_dicio(dicio)
    dicio_resultado = dict()
    tamanho = 0
    for ch, val in dicio_ingredientes.items():
        if len(val) > tamanho:
            dicio_resultado.clear()
            dicio_resultado[ch] = val
            tamanho = len(val)
        elif len(val) == tamanho:
            dicio_resultado[ch] = val
        print(dicio_resultado)
    return dicio_resultado
Como se pode ver, cada vez que encontramos uma solução melhor, limpamos o dicionário (método clear) e actualizamos de seguida.

quarta-feira, 22 de novembro de 2017

Teste # 2 - TP2

P2

O problema: Duas palavra dizem-se deslocadas de n posições se se puder obter os caracteres de uma deslocando cada caracter da outra n posições no alfabeto. Por exemplo, HAL e IBM são palavras deslocadas com uma distância de 1. Escreva um programa que determina se duas palavras são deslocadas ou não de n.

A solução. Definimos o alfabeto e depois procuramos as posições no alfabeto dos caracteres presentes nas duas palavras. É preciso que todas as diferenças tenham o mesmo valor, igual a n.

def deslocadas(pal_1, pal_2,n):
    """ 
    pal_1 é pal_2 quand se deslocam todos os caracteres de n posições?
    Exemplo: IBM,HAL e n = 1.
    """
    alfabeto ='abcdefghijklmnopqrstuvwxyz'
    for i in range(len(pal_1)):
        indice_1 = alfabeto.find(pal_1[i])
        indice_2 = alfabeto.find(pal_2[i])
        if (indice_1 - indice_2) != n:
            return False
    return True
Notar que a ordem porque efectuamos a comparação dos índices é relevante. Se n for positivo a primeira palavra deve estar à esquerda da segunda, se for negativo é o contrário. Se quisermos não nos preocupar com a ordem e usar sempre valores positivos de n só temos que acrescentar:
def desloc(p_1,p_2,n):
    return (deslocadas(p_1, p_2,n) or deslocadas(p_2, p_1,n)
P3

O problema: Suponha que tem duas imagens a preto e branco e pretende saber se uma delas, a mais pequena,ocorre na imagem maior. Escreva um programa que resolve esta questão.

A solução. O problema desdobra-se em dois sub-problemas: saber se a operação é possível e, no caso afirmativo, calcular a ocorrência do canto superior esquerdo. Para simplificar a questão criámos um pequeno programa auxiliar que determina se duas imagens são iguais ou não.

def ocorre(img_1, img_2):
    # possivel
    n_linhas_1 = len(img_1)
    n_colunas_1 = len(img_1[0])
    n_linhas_2 = len(img_2)
    n_colunas_2 = len(img_2[0]) 
    possivel = (n_linhas_1 <= n_linhas_2) and (n_colunas_1 <= n_colunas_2)
    # verifica
    for l in range(n_linhas_2 - n_linhas_1 + 1):
        for c in range(n_colunas_2 - n_colunas_1 + 1):
            # verifica se imagem 1 ocorre a partir da posição (l,c)
            if igual(img_1,img_2,l,c):
                return (l,c)
    return (-1,-1)

def igual(img_1,img_2,l,c):
    for i in range(len(img_1)):
        for j in range(len(img_1[0])):
            if img_1[i][j] != img_2[l+i][c+j]:
                return False
    return True

Teste # 2 - TP1

P2

O problema: Um palavra diz-se embebida noutra palavra se os seus caracteres ocorrerem nesta pela mesma ordem, embora não necessariamente de modo consecutivo. Por exemplo, ADIA está embebida em ACADEMIA. Escreva um programa que dadas duas palavras, determina se uma delas está embebida na outra.

A solução. Não há grande mistério. Analisamos os caracteres da palavra mais pequena e verificamos se está presente na palavra maior. Para garantir o problema da ordem cada vez que fazemos a pesquisa limitarmos o campo de procura de modo que a posição inicial seja a posição imediatamente à frente da posição onde se encontrou o último caractere. notar que mal um caractere não esteja presente o programa termina com False.
def embebida(pal_1, pal_2):
    """ verifica se pal_1 está embebida em pal_2."""
    pos = 0
    for car in pal_1:
        indice = pal_2.find(car,pos)
        if indice == -1:
            return False
        pos = indice + 1
    return True
P3

O problema: Suponha que tem duas imagens a preto e branco e pretende que uma delas, a mais pequena, substitua uma parte da imagem maior. Escreva um programa que dadas as duas imagens e o ponto de inserção efectue a modificação.

A solução. A solução apresentada tem duas partes: uma em que se verifica se a operação é possível, e a segunda em que se processa a alteração. como se pode ver a estratégia consistiu em percorrer a imagem mais pequena e colocar os seus valores no sítio certo da imagem maior.

def altera_img(img_1, img_2,pos_l, pos_c):
    """ altera a imagem 2 com a imagem 1 a partir das posição (pos_l,pos_c)"""
    # é possível?
    n_linhas_1 = len(img_1)
    n_colunas_1 = len(img_1[0])
    n_linhas_2 = len(img_2)
    n_colunas_2 = len(img_2[0]) 
    possivel = ((pos_l + n_linhas_1) <= n_linhas_2) and ((pos_c + n_colunas_1) <= n_colunas_2)
    if possivel:
        # modifica
        for l in range(n_linhas_1):
            for c in range(n_colunas_1):
                img_2[pos_l + l][pos_c + c] = img_1[l][c]
    return img_2

sábado, 18 de novembro de 2017

Variações em torno de vogais

Nas aulas discutimos o problema de escrever um programa que leia um texto e indique para cada vogal a lista das suas ocorrências no texto. Trata-se de um problema em que, naturalmente, se opta por um dicionário para representar o resultado. Assim, para:
txt = 'ernesto ui ui cuidado com os alunos’
o resultado deve ser:
{'e': [0, 3], 'o': [6, 20, 23, 26, 33], 'u': [8, 11, 15, 31], 'i': [9, 12, 16], 'a': [18, 29]}
A solução evidente segue o padrão ciclo-acumulador. Aqui o acumulador é o dicionário que vai sendo actualizado à medida que percorremos o texto caractere a caractere.
def vogais_x(texto):
    dicio = dict()
    for i in range(len(texto)):
        if texto[i] == 'a':
            dicio['a'] = dicio.get('a',[]) + [i]
        elif texto[i] == 'e':
            dicio['e'] = dicio.get('e',[]) + [i]
        elif texto[i] == 'i':
            dicio['i'] = dicio.get('i',[]) + [i]
        elif texto[i] == 'o':
            dicio['o'] = dicio.get('o',[]) + [i]
        elif texto[i] == 'u':
            dicio['u'] = dicio.get('u',[]) + [i]  
        else:
            continue
    return dicio
Todos concordaremos que é uma solução feia. Todos aqueles ifs podem ser facilmente removidos. Por outro lado, sabemos que vamos precisar não apenas das posições mas também dos caracteres, pelo que nos interessa percorrer o texto obtendo estes dois elementos. Daí uma nova versão:
def vogais_y(texto):
    vogais ='aeiou'
    dicio = dict()
    for i,car in enumerate(texto):
        if texto[i] in vogais:
            dicio[car] = dicio.get(car,[]) + [i]
    return dicio 
Estas soluções constroem o dicionário de modo incremental. No entanto, nós sabemos quais são as chaves do dicionário, e sabemos ainda que nunca mudam. Daí , a possibilidade de criar inicialmente o dicionário com as posições todas iguais a listas vazias, recorrendo ao método fromkeys. Como consequência deixamos de necessitar do uso do método get.
def vogais(texto):
    vogais ='aeiou'
    dicio = dict.fromkeys(vogais, [])
    for i,car in enumerate(texto):
        if car in vogais:
            dicio[car] = dicio[car] + [i]
    return dicio  
O leitor atento dirá que ainda se pode ter uma solução mais elegante usando uma atribuição aumentada: +=.
def vogais_z(texto):
    vogais ='aeiou'
    dicio = dict.fromkeys(vogais, [])
    for i,car in enumerate(texto):
        if car in vogais:
            dicio[car] += [i] # <——— 
    return dicio 
No entanto, se correr este código verificará que não funciona. Ou melhor, corre mas apresenta o resultado errado:
{'a': [0, 3, 6, 8, 9, 11, 12, 15, 16, 18, 20, 23, 26, 29, 31, 33], 'e': [0, 3, 6, 8, 9, 11, 12, 15, 16, 18, 20, 23, 26, 29, 31, 33], 'i': [0, 3, 6, 8, 9, 11, 12, 15, 16, 18, 20, 23, 26, 29, 31, 33], 'o': [0, 3, 6, 8, 9, 11, 12, 15, 16, 18, 20, 23, 26, 29, 31, 33], 'u': [0, 3, 6, 8, 9, 11, 12, 15, 16, 18, 20, 23, 26, 29, 31, 33]} 
A explicação para o resultado (todas as vogais ocorrem nas mesmas posições, igual à união das posições de cada uma…) é simples: a construção do dicionário usando fromkeys faz com que os valores iniciais sejam o mesmo objecto (partilha de memória) e a instrução += não constrói objectos novos. Uma solução consiste em alterar a construção do dicionário inicial, usando dicionários por compreensão:
def vogais_e(texto):
    vogais ='aeiou'
    dicio = {vog:[] for vog in 'aeiou'}
    for i,car in enumerate(texto):
        if car in vogais:
            dicio[car] += [i]
    return dicio 
E pronto. Espero que tenha entendido. Ah, já agora, ainda outra solução….
def vogais_d(texto):
    vogais ='aeiou'
    dicio = {vog:[] for vog in 'aeiou'}
    for i,car in enumerate(texto):
        if car in vogais:
            dicio[car].append(i)
    return dicio

segunda-feira, 23 de outubro de 2017

Teste #1 - TP4

P1

O seguinte programa possui um ou mais erros/omissões. Identifique o(s) erro(s)/omissão(ões)relacionando-o(s) com o conceito de espaço de nomes do python.
def  polar_to_cart_x(r, ang):
    x = r * math.cos(ang)
    return x
if  __name__  == “__main__":
    print(polar_to_cart_x (10, pi/4))
Não é feita a importação do módulo math e devemos usar math.pi e não pi.

P2

Escreva um programa para contar o número de pontos que estão dentro de uma circunferência de raio r centrada em (0, 0). O programa deverá receber o número de pontos atestar (num) e o raio (r) como parâmetros. As coordenadas de cada ponto deverão ser pedidas interativamente ao utilizador. No final, o programa deverá indicar o número de pontos que estão dentro da circunferência.
import math

def dentro(raio, x, y):
    len = math.sqrt(x ** 2 + y ** 2)
    if len <= raio:
        return True
    else:
        return False
    

def conta_dentro_circulo(num, raio):
    num_dentro = 0
    for i in range(num):
        x = eval(input("x: "))
        y = eval(input("y: "))
        
        if dentro(raio, x, y):
            num_dentro = num_dentro + 1
    
    return num_dentro
            
    
if __name__ == "__main__":
    print(conta_dentro_circulo(3, 20))
P3

Usando o módulo turtle, escreva um programa que lhe permita desenhar formas do tipo da ilustrada na Figura. Cada forma é composta por um rectângulo com uma circunferência interna, centrados no mesmo ponto.O raio da circunferência dependerá das dimensões do rectângulo, de acordo com o ilustrado na figura. O programa deverá ser parametrizável de maneira a permitir escolher o número de formas, as dimensões mínima e máxima dos lados dos rectângulos, bem como as posições (x e y) dos mesmos. As coordenadas e dimensões do rectângulo envolvente de cada forma deverão ser geradas aleatoriamente.Soluções modulares serão valorizadas.
import turtle
import random

def rectangulo(xr, yr, lado1, lado2):
    #preparar
    turtle.penup()
    turtle.goto(xr,yr)
    turtle.pendown()
    
    #desenhar
    for i in range(4):
        if i%2 == 0:
            turtle.forward(lado1)
        else:
            turtle.forward(lado2)
        turtle.right(90)
        
def circulo(xc, yc, raio):
    #preparar
    turtle.penup()
    turtle.goto(xc, yc)
    turtle.pendown()
    
    #desenhar
    turtle.circle(raio)
    

def forma_rect_circulo(xf, yf, lado1, lado2):
    #rectangulo
    rectangulo(xf, yf, lado1, lado2)

    #circulo no interior
    raio = min(lado1, lado2) / 2
    xc = xf + lado1/2
    yc = yf - lado2/2 - raio
    circulo(xc, yc, raio)
    
    
def desenha_formas(num, xmin, xmax, ymin, ymax, ladomin, ladomax):
    
    #sortear e desenhar
    for i in range(num):
        xf = random.randint(xmin, xmax)
        yf = random.randint(ymin, ymax)
        lado1 = random.randint(ladomin, ladomax)
        lado2 = random.randint(ladomin, ladomax)
        forma_rect_circulo(xf, yf, lado1, lado2)
    
    
if __name__ == "__main__":
    turtle.hideturtle()   
    desenha_formas(5, -300, 300, -300, 300, 50, 150)
    turtle.exitonclick()

domingo, 22 de outubro de 2017

Exercícios de Programação Descendente (II)

Nas aulas foi colocado o problema de visualizar na forma de um histograma o resultado de uma experiência de lançamento de um dado. O histograma permitia saber quantas vezes saiu cada número. Pretende-se algo como a figura ilustra.

Vamos novamente tentar perceber se podemos dividir o problema em sub-problemas de modo a tornar a nossa missão mais fácil. Perante o anunciado é evidente que temos dois sub-problemas: (1) efectuar a experiência contando o número de vezes que saiu cada número e, (2) usar essa informação para construir o histograma. As condições do enunciado forçam a usar o modulo turtle para a visualização! É clara a existência de uma dependência entre os dois sub-problemas, pelo que antes de começarmos a resolver em separado cada um deles precisamos definir o seu interface. Uma opção que se impõe é que o sub-problema (1) depende do número de lançamentos n para construir um tuplo (n1,n2,n3,n4,n5,n6), com ni igual ao número de vezes que saiu o número i, e n1+n2+n3+n4+n5+n6 = n. A escolha de um tuplo é natural pois precisamos de um contentor, com a função de memória. Tomadas estas decisões, podemos passar a concretização do programa.
import turtle

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    pass


def histograma(res):
    pass


if __name__ == '__main__':
    n = 100
    dados_histo(n)
Vamos começar por resolver o segundo sub-problema. Para tal, vamos de novo decompor o sub-problema em sub-problemas. Aqui temos, pelo menos, duas opções: (a) numa leitura “vertical”, temos quatro sub-problemas: escreve os números de 1 a 6, desenha um traço, desenha os rectângulos e escreve os números correspondentes aos números de vezes que saiu cada número; (b) numa leitura “horizontal, temos seis sub-problemas idênticos: escrever um número, desenhar um traço, desenhar uma coluna e, novamente, escrever um número. A nossa escolha vai ser a segunda, pois é aquela que nos permite ter mais graus de liberdade.
import turtle

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    pass

def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha

def desenha(i,alt, posx,posy,comp_linha):
    pass

if __name__ == '__main__':
    n = 100
    teste = (46,39,105,0,44,5)
    histograma(teste)
    #dados_histo(n)
No esboço de solução apresentado podemos verificar que o programa histograma se limita a desenhar cada caso em sequência. Note como conseguimos os valores do número e do número de vezes que saiu graça ao uso de enumerate. Note ainda que a opção tomada obriga a que cada caso singular tenha que saber as quatro componentes relevantes: posição, tamanho do traço, numero do dado e numero de vezes que saiu. A posição para desenhar a coluna vai ser o centro pelo que pode ser calculada a partir do conhecimento do tamanho do traço. Tal como está, podemos testar o programa … mesmo que este não faça nada! Esta é uma das vantagens da programação descendente: podemos testar primeiro as soluções para os problemas e depois de as integrarmos no programa principal, testar o programa principal. A eliminação de eventuais erros é deste modo mais fácil de fazer. Passemos ao caso mais básico. São quatro so sub-problemas básicos que o compõem: número, linha, coluna, número. No entanto os dois problema de escrita de um número são na realidade o mesmo.
def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i   
    # desenha linha
    # desenha coluna
    # escreve número de vezes (alt) que saiu o número i  
    pass
Podemos resolver cada um destes três sub-problemas e testá-los isoladamente.
def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.color(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
Agora precisamos de integrar esses sub-problemas no sub-problema de desenho de uma componente. Aqui vamos ter que perceber as relações entre cada um dos sub-componentes. Em primeiro lugar, decidimos que a largura da coluna será igual a metade do comprimento do traço. Em segundo lugar, fixamos a fonte no tamanho 12 e controlamos a posição da escrita.
def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha linha
    linha(posx,posy,comp_linha,'red')
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')
Podemos agora testar o problema de visualizar. E está na hora de ver tudo junto.
import turtle

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    pass


def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha

def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha linha
    linha(posx,posy,comp_linha,'red')
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')


def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.color(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
  
    
    
    
if __name__ == '__main__':
    n = 100
    teste = (46,39,105,0,44,5)
    fonte = ('Arial', 24, 'bold')
    posx = 0
    posy = 0 
    cor_1 = 'black'
    cor_2 = 'red'
    comp = 80
    lado_1 = comp/2
    lado_2 = 50
    #escreve_numero(posx,posy,fonte,n,cor)
    #linha(posx,posy,comp,cor_2)
    #coluna(posx,posy, lado_1, lado_2,cor_2)
    #desenha(4,100, posx,posy,comp)
    histograma(teste)
    #dados_histo(n)
    turtle.exitonclick()
Agora é a vez do primeiro sub-problema, simular o lançamento do dado. Já fizemos isso em problemas anteriores semelhantes. Aqui a novidade reside no facto de querermos memorizar os resultados. Dado o facto de estarmos a usar tuplos, que são objectos imutáveis, vamos decompor esta questão em duas: (1) guardar os valores saídos em cada lançamento, (2) contar quantas vezes saiu cada um. Solução óbvia:
def lanca_dado(n):
    # lançamento
    resultado = tuple()
    for i in range(n):
        numero = random.randint(1,6)
        resultado = resultado + (numero,)
    # contagem
    conta = tuple()
    for i in range(1,7):
        conta_i = resultado.count(i)
        conta = conta + (conta_i,)
    return conta
Podemos finalmente testar o programa completo.
import turtle
import random

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    # lançamento
    resultado = tuple()
    for i in range(n):
        numero = random.randint(1,6)
        resultado = resultado + (numero,)
    # contagem
    conta = tuple()
    for i in range(1,7):
        conta_i = resultado.count(i)
        conta = conta + (conta_i,)
    return conta


def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha

def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha linha
    linha(posx,posy,comp_linha,'red')
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')


def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.color(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
  
    
    
    
if __name__ == '__main__':
    n = 100
    teste = (46,39,105,0,44,5)
    fonte = ('Arial', 24, 'bold')
    posx = 0
    posy = 0 
    cor_1 = 'black'
    cor_2 = 'red'
    comp = 80
    lado_1 = comp/2
    lado_2 = 50
    #escreve_numero(posx,posy,fonte,n,cor)
    #linha(posx,posy,comp,cor_2)
    #coluna(posx,posy, lado_1, lado_2,cor_2)
    #desenha(4,100, posx,posy,comp)
    #histograma(teste)
    #print(lanca_dado(n))
    dados_histo(n)
    turtle.exitonclick()
    
    
Para concluir o exercício, experimente com diferentes valores de tentativas. Que conclusões pode tirar à medida que n aumenta?? Identifique os pontos em que a solução não é genérica. como pode alterar a situação??

Na sala, alguns disseram que nos histogramas as colunas não estão separadas. A adaptação do código feito para que a visualização seja essa é mínima: retirar a linha na definição desenha, separar a cor do traço (pencolor) da cor de preenchimento (fillcolor) em desenha para que as colunas fiquem claramente a ver-se e, na função histograma, alterar o posicionamento ao longo do eixo dos xx da cada coluna.
import turtle
import random

def dados_histo(n):
    # experiência de lançamentos
    res = lanca_dado(n)
    # visualizaçao
    histograma(res)
    
    
def lanca_dado(n):
    # lançamento
    resultado = tuple()
    for i in range(n):
        numero = random.randint(1,6)
        resultado = resultado + (numero,)
    # contagem
    conta = tuple()
    for i in range(1,7):
        conta_i = resultado.count(i)
        conta = conta + (conta_i,)
    return conta


def histograma(res):
    posx = -100
    posy = 0
    comp_linha = 80
    for i,alt in enumerate(res):
        # desenha caso i
        desenha(i+1,alt,posx,posy,comp_linha)
        # define parâmetros
        posx = posx + comp_linha/2

def desenha(i,alt, posx,posy,comp_linha):
    # escreve número i
    escreve_numero(posx + comp_linha/2,posy - 15,('Arial',12,'bold'),i,'black')    
    # desenha coluna
    coluna(posx+comp_linha/4,posy,comp_linha/2,alt,'red')
    # escreve número de vezes (alt) que saiu o número i  
    escreve_numero(posx + comp_linha/2 - 5,posy + alt ,('Arial',12,'bold'),alt,'black')


def coluna(posx,posy, lado_1, lado_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    turtle.pencolor('black')
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado_1)
        turtle.left(90)
        turtle.forward(lado_2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
    
def linha(posx,posy,comp_linha,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)
    turtle.pendown()
    # linha
    turtle.forward(comp_linha) 
    turtle.hideturtle()
    
def escreve_numero(posx,posy,fonte,valor,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.color(cor)  
    turtle.pendown()
    # escreve
    turtle.write(valor,font=fonte)
    turtle.hideturtle()
  
    
    
    
if __name__ == '__main__':
    n = 1000
    teste = (46,39,105,0,44,5)
    fonte = ('Arial', 24, 'bold')
    posx = 0
    posy = 0 
    cor_1 = 'black'
    cor_2 = 'red'
    comp = 80
    lado_1 = comp/2
    lado_2 = 50
    #escreve_numero(posx,posy,fonte,n,cor)
    #linha(posx,posy,comp,cor_2)
    #coluna(posx,posy, lado_1, lado_2,cor_2)
    #desenha(4,100, posx,posy,comp)
    #histograma(teste)
    #print(lanca_dado(n))
    dados_histo(n)
    turtle.exitonclick()
    
Para o leitor: e se quisermos que o histograma possa ter uma orientação qualquer??

sábado, 21 de outubro de 2017

Verdades...

Pense nisto ...

Exercícios de Programação Descendente (I)

Durante as aulas foi colocado o problema de desenvolver uma solução para a criação da imagem visual do símbolo da radioactividade.
Perante este problema a reacção primeira deve ser a de equacionar a possibilidade de decompor o problema em sub-problemas, se possível, independentes. Caso não sejam independentes temos que acordar primeiro o modo como se relacionam.

Não creio ser difícil identificar três sub-problemas: desenhar um quadrado, desenhar três sectores e desenhar uma circunferência. A dependência neste caso é a posição relativa de cada uma das três componentes. Com base nesta abordagem podemos escrever um primeiro esboço de solução.
import turtle

def radioactividade():
    # desenha quadrado
    quadrado()
    # desenha sectores
    sectores()
    #desenha circunferência
    circunferencia()
    
    
def quadrado():
    pass

def sectores():
    pass

def circunferencia():
    pass

if __name__ == '__main__':
    radioactividade()
Esta solução já pode ser testada embora não faça nada! Note-se que as definições ainda não têm argumentos… Vamos tomar nova decisão: dar o máximo liberdade à resolução de cada um dos sub-problemas. Por exemplo, no caso do quadrado, vamos criar uma definição que permita desenhar um quadrado parametrizado pelo tamanho do lado, a posição, a orientação e a cor. Já sabemos como fazer isso.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    sectores()
    #desenha circunferência
    circunferencia()
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores():
    pass

def circunferencia():
    pass

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 30
    cor = 'yellow'
    radioactividade(lado,posx,posy,orientacao,cor)
Podemos testar isoladamente a definição quadrado e/ou testá-la no contexto da definição do símbolo da radioactividade. O normal, em programas grandes é que o teste seja feito primeiro isoladamente e só depois no interior do programa principal. Trata-se de mais uma vantagem deste tipo de programação, apelidada de programação descendente, que conduz a soluções modulares. Para além de os testes serem mais fáceis de fazer, também ficamos com código reutilizável!

Resolvida esta questão vamos escolher um dos dois sub-problemas restantes. Optamos pela circunferência. Também aqui já sabemos o que fazer.
def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Se testarmos esta definição verificamos que está tudo funcional como pretendido. Naturalmente queremos testar esta solução no interior do nosso programa principal, e aqui, somos confrontados com o facto dos tamanhos (lado e raio), das posições e das orientações estarem relacionadas. Com um pouco de análise não será difícil chegar a uma solução aceitável.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor, cor_c):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    sectores()
    #desenha circunferência
    raio = lado/10
    posx_c = posx + lado/2 + raio
    posy_c = posy + lado/2
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores():
    pass

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 0
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    #circunf(raio,posx,posy,orientacao,cor)
    radioactividade(lado,posx,posy,orientacao,cor, cor_c)
    turtle.exitonclick()
Como se pode ver o desenho da circunferência é precedido do cálculo dos valores apropriados para o tamanho do raio, a posição e a orientação. Repare que para que o centro da circunferência coincida com o centro do quadrado, a tartaruga tem que ser colocada numa posição em que veja esse centro à sua esquerda e à distância do raio! O leitor atento notará que a orientação inicial é de zero graus, pois é isto que nos é pedido. Caso o valor seja diferente o posicionamento do centro da circunferência também é diferente.

Deixámos para o fim o problema dos sectores. Também aqui é possível decompor este problema em três sub-problemas equivalentes. Vejamos então como podemos desenhar um sector com total liberdade.
def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()
O desenho tem três partes: desenho do raio, desenho do arco, desenho do raio. Notar que no final queremos ter a tartaruga com a orientação inicial. Porquê?? Uma vez mais, um leitor atento pode ser levado a pensar que o desenho da circunferência e o desenho do sector poderiam ser unidos numa única definição. Afinal, parece que apenas diferem do parâmetro amplitude. Mas será mesmo assim? Quem quiser pode explorar esse caminho e verificar as questões que se colocam.

Como desenhamos os três sectores? Não é difícil perceber que temos que repetir o desenho de um sector alterando apenas a orientação de 120 graus. Será que o facto de a tartaruga que desenha um sector terminar com a mesma orientação que a inicial ajudou???
def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
Para terminar o trabalho temos que incorporar esta solução no programa principal.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    raio_s = lado/4
    posx_s = posx + lado/2
    posy_s = posy + lado/2
    orientacao_s = orientacao
    amplitude = 60
    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
    #desenha circunferência
    raio = lado/10
    posx_c = posx + lado/2 + raio
    posy_c = posy + lado/2
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
        

def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 0
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    raio_s = lado/4
    cor_s = 'black'
    amplitude = 60
    #circunferencia(raio,posx,posy,orientacao,cor)
    #sector(raio_s,posx,posy,orientacao,cor_s,amplitude)
    #sectores(raio_s,posx,posy,orientacao,cor_s,amplitude)
    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
    turtle.exitonclick()
A definição de alguns parâmetros dos sectores em função dos parâmetros do quadrado não deve oferecer dúvidas. Se executarmos o programa verificamos que não aparece a separação entre a circunferência e os sectores. A solução desse problema é trivial e passa por colocar a cor da caneta da tartaruga, quando desenha a circunferência, a branco.

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pencolor('white')
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Como dissemos atrás esta solução global funciona quando a orientação inicial é zero. Se for outra não funciona. Como alterar a nossa solução mexendo o mínimo possível, por forma ao programa funcionar mesmo quando o quadrado tem uma orientação qualquer? A solução passa por recalcular o centro da figura tendo a orientação em linha de conta. Para isso basta usar um pouco do nosso conhecimento de trigonometria.
import turtle
import math


def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    raio_s = lado/4
    ang_base = (orientacao* math.pi / 180)
    ang = ang_base + math.pi/4     
    posx_s = posx + lado/math.sqrt(2) * math.cos(ang) 
    posy_s = posy + lado/math.sqrt(2) * math.sin(ang)
    orientacao_s = orientacao
    amplitude = 60
    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
    #desenha circunferência
    raio = lado/10
    posx_c = posx_s +  raio * math.cos(ang_base)
    posy_c = posy_s + raio * math.sin(ang_base)
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
        

def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pencolor('white')
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 200
    posx = -50
    posy = -50
    orientacao = 45
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    raio_s = lado/4
    cor_s = 'black'
    amplitude = 60
    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
    turtle.exitonclick()
Como se pode ver apenas mexemos no programa principal e em zonas localizadas do código, as zonas de definem a interacção entre as partes. E pronto. Espero que da próxima vez que programar procure usar este princípio da decomposição de um problema em sub-problemas!

sexta-feira, 13 de outubro de 2017

Teste # 1 - TP2

P1

O que aparece no lugar do ponto de interrogação?
>>> x = 'abacadabra'
>>> x[1] = 'zeus'
Traceback (most recent call last):
  Python Shell, prompt 2, line 1
builtins.TypeError: 'str' object does not support item assignment
Aparece um erro pois as cadeias de caracteres são imutáveis não podendo o seu valor ser alterado.

P2

Como saber se uma moeda está enviesada? Fazemos vários lançamentos e comparamos com o valor esperado para uma das duas opções. Como nada neste mundo é perfeito aceitamos uma pequena discrepância em relação a esse valor. Para resolver o problema, vamos devagar e por partes. Primeiro uma versão simples que apenas simula e compara com o caçoe médio esperado.
def enviesada_a(n):
    # lança e conta
    conta = 0
    for i in range(n):
        conta = conta + random.randint(0,1)   
    # analisa
    return conta != n//2
Esta versão baseia-se num padrão dec programação conhecido por ciclo - acumulador. O nome conta está associado a um objecto cujo valor corresponde ao numero de vezes que já saiu caras (1). O ciclo é repetido o número de vezes pretendido. A comparação final é feita usando a divisão inteira.

Vamos partir desta solução para a solução final pretendida. A moeda estará enviesada se o valor obtido estiver fora de um dado intervalo.
def enviesada(n,perc):
    """ 
    n = número de lançamentos
    perc = percentagem aceitável [0,1]
    """
    # lança e conta
    conta = 0
    for i in range(n):
        conta = conta + random.randint(0,1)   
    # analisa
    espera = n/2
    inf_ = (1 - perc)* espera
    sup_ = (1 + perc) * espera
    return (conta < inf_) or (conta > sup_)
Como se observa usamos agora uma divisão de floats.

P3

Queremos desenhar bonecos como o da figura.
Olhando para a figura observamos que precisamos saber desenhar balões coloridos e uma cauda que é composta de repetições de uma sequência de quatro segmentos com orientações alternadas. Uma solução simples vai envolver três passos:
def boneco(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    # desenha balão grande
    # desenha balão pequeno
    # desenha cauda
    pass
Tratemos dos balões isoladamente:
def bola(raio, posx,posy, orientacao, cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.fillcolor(cor)
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Esta solução corresponde ao que já foi feito nas aulas!!! Vamos tratar da parte nova: a cauda. olhando para a figura vemos que é composta a partir de uma sequência de formas mais simples. Estas por sua vez são formadas por três traços. Eis uma solução genérica para a cauda:
def cauda(n, tipo, posx, posy,orientacao, comp, cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.pencolor(cor)
    for i in range(n):
        turtle.forward(comp)
        turtle.right(tipo * 60)     
    turtle.hideturtle()
Dizemos que é genérica porque usamos o parâmetro n, que no caso que nos interessa será igual a 3. Por outro lado, note-se que o parâmetro tipo é usado para determinar a orientação de cada sequência de três segmentos. tipo pode valer 1 ou -1, pois só temos duas orientações a considerar.

Resolvidas as três questões (balão grande, balão pequeno e cauda), vamos juntar tudo. A primeira questão é a de saber como juntamos os dois balões. A ideia é desenhar o maior e depois, a partir da posição final e da orientação, calcular a posição do centro do balão pequeno. Uma hipótese é:
def boneco(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    # desenha balão grande
    bola(raio_1,  posx,posy, orientacao, cor_1)
    # desenha balão pequeno
    turtle.penup()
    turtle.setheading(orientacao-90)
    turtle.forward(2*raio_2)
    turtle.setheading(orientacao)
    turtle.pendown()
    bola(raio_2,  turtle.xcor(),turtle.ycor(), orientacao, cor_2)
    # desenha cauda
   
Esta solução é fácil de entender se nos lembrarmos que a tartaruga desenha uma circunferência tendo o centro à sua esquerda! Claro que podemos fazer de outro modo:

def balao(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    bola(raio_1,  posx,posy, orientacao, cor_1)
    bola(-raio_2,  turtle.xcor(),turtle.ycor(), orientacao, cor_2)
Percebe a diferença???

Só falta acrescentar a cauda…
def balao(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    bola(raio_1,  posx,posy, orientacao, cor_1)
    turtle.penup()
    turtle.setheading(orientacao-90)
    turtle.forward(2*raio_2)
    turtle.setheading(orientacao)
    turtle.pendown()
    bola(raio_2,  turtle.xcor(),turtle.ycor(), orientacao, cor_2)
    for i in range(n):
        cauda(3,(-1)**i,turtle.xcor(),turtle.ycor(),turtle.heading(),20,cor_3)
    turtle.hideturtle()
E pronto! Percebeu o modo como alternamos a orientação da cauda??? Simplesmente fazendo o tipo igual a (-1)** i, o que faz com que o tipo vá ser alternadamente 1 e -1, como pretendido!

Pode usar este programa para criar variantes. Por exemplo:

def baloes(n,raio_1, raio_2, posx,posy, orientacao, cor_1,cor_2,cor_3):
    # balão grande
    bola(raio_1,  posx,posy, orientacao, cor_1)
    # n balões pequenos à volta do balão grande...
    for i in range(n):
        turtle.penup()
        turtle.circle(raio_1,360/n)
        turtle.pendown()        
        bola(-raio_2,  turtle.xcor(),turtle.ycor(), turtle.heading(), cor_2)
    turtle.hideturtle()

Teste # 1 - TP1

P1

Quando fazemos :
>>> X = X + 1 
acontece o seguinte. Primeiro o sistema tenta calcular o objecto associado à expressão X + 1. Para tal procura no espaço dos objectos o valor do objecto associado ao nome X. De seguida, incrementa esse valor de uma unidade e associa o novo objecto ao nome X.

P2

Era-nos pedido uma solução para o problema de saber se após o lançamento de um dado n vezes, o número de vezes que saiu um número par é maior do que o valor médio esperado. Podemos resolver este problema pro aproximações, baseando-nos num padrão de programação nosso conhecido: ciclo - acumulador.

def par_impar(n):
    conta_par = 0 # o acumulador
    for i in range(n):
        num = lanca_dado()
        # actualiza o acumulador
    # define resultado
A implementação da simulação do lançamento do dado é trivial:
import random 

def lanca_dado():
    return random.randint(1,6)
Com estes elementos chegamos facilmente à versão final:
import random

def lanca_dado():
    return random.randint(1,6)

def par_impar(n):
    conta_par = 0
    for i in range(n):
        num = lanca_dado()
        if (num == 2) or (num == 4) or (num == 6): # if num in (2,4,6):
            conta_par = conta_par + 1
    if conta_par > n/2:
        return True
    else:
        return False
Notar que o teste de saída de número par pode ser abreviado para if num in (2,4,6):

P3

Queremos um programa que nos permita desenhar figuras como a abaixo.

Pedem para poder parametrizar muita coisa: posição, orientação, número de laços e de segmentos tos, cor, tamanho do lado dos laços, tamanho dos segmentos, etc.

A solução passa por dividir o problema em sub-problemas e não tentar resolver tudo de uma vez. Olhando para a figura vemos laços e uma cauda. Os laços podem ser construídos como dois triângulos ligados por um vértice, enquanto a cauda é uma sequência de segmentos. Vamos resolver cada um dos sub-problemas.

Comecemos pelos triângulos coloridos, algo que fizemos nas aulas.
def tri_cor(posx,posy,orientacao,lado,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(3):
        turtle.forward(lado)
        turtle.left(120)
    turtle.end_fill()
    turtle.hideturtle()
Como se pode ver, iniciamos o programa definindo os parâmetros e depois desenhos o triângulo. O laço resulta de desenharmos dois triângulos percebendo que a orientação de ambos está desfasada de 180 graus.

def laco(posx,posy,orientacao,lado,cor):
    tri_cor(posx,posy,orientacao,lado,cor)
    tri_cor(posx,posy,orientacao + 180,lado,cor)
    turtle.hideturtle()
Passemos à cauda como sequência de segmentos. Desenhar um segmento com uma dada inclinação é trivial.

def seg(posx,posy,comp,orientacao,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orientacao)
    turtle.pencolor(cor)
    turtle.forward(comp)
    turtle.hideturtle()
Podemos agora juntar as peças do nosso puzzle. Se pensarmos um pouco, a estratégia mais interessante para o nosso programa final consiste em desenhar um segmento e de seguida desenhar um laço, repetindo estas acções o número apropriado de vezes. Daí a solução:

def boneco(n, posx, posy, orientacao,inc,comp,lado,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    for i in range(n):
        if i % 2 == 0:
            seg(turtle.xcor(),turtle.ycor(),comp,orientacao + inc,cor)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()-90,lado,cor)
        else:
            seg(turtle.xcor(),turtle.ycor(),comp,orientacao - inc,cor)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()+90,lado,cor)        
    if n % 2 == 0:
        seg(turtle.xcor(),turtle.ycor(),comp,orientacao + inc,cor)
    else:
        seg(turtle.xcor(),turtle.ycor(),comp,orientacao - inc,cor)
E pronto! O leitor atento notará que o if final se deve à necessidade de desenhar o último segmento da cauda. Por outro lado, note como controlamos a orientação da cauda, e como relacionamos a orientação dos segmentos e dos laços. Finalmente, a cor dos laços e dos segmentos é a mesma, mas é trivial fazer com que tenham cor diferente!

Depois de feito o programa, torna-se evidente que podemos simplificar o desenho dos segmentos:
def boneco(n, posx, posy, orientacao,inc,comp,lado,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.pencolor(cor)
    for i in range(n):
        if i % 2 == 0:
            turtle.setheading(orientacao + inc)
            turtle.forward(comp)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()-90,lado,cor)
        else:
            turtle.setheading(orientacao - inc)
            turtle.forward(comp)
            laco(turtle.xcor(),turtle.ycor(),turtle.heading()+90,lado,cor)
    if n % 2 == 0:
        turtle.setheading(orientacao + inc)
        turtle.forward(comp)
    else:
        turtle.setheading(orientacao - inc)
        turtle.forward(comp) 
E chega… ou talvez não!

Para terminar, e embora não fosse necessário, um pequeno programa para desenhar apenas uma cauda ondulante:
def cauda(n,posx,posy,comp,orienta_1,orienta_2,cor):
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.pencolor(cor) 
    for i in range(n):
        if i % 2 == 0 :
            seg(turtle.xcor(),turtle.ycor(),comp, orienta_1,cor)
        else:
            seg(turtle.xcor(),turtle.ycor(),comp, orienta_2,cor)
Note-se como controlamos a orientação dos segmentos.

sábado, 30 de setembro de 2017

Zen ou a Arte de Programar

(brincadeiras de um sábado de manhã)


Suponham que lhe pedem um programa capaz de desenhar o símbolo do Yin-Yang:
Por onde começar?? Olhando para a figura verificamos que é composta à base de circunferências e semi-circunferências a duas cores (preto e branco). Usando um princípio já nosso conhecido, tentemos simplificar a questão, esquecendo os círculos interiores e as cores:
def zen1(raio):
    turtle.setheading(90)
    turtle.circle(raio)
    turtle.circle(raio/2,-180)
    turtle.setheading(90)
    turtle.circle(raio/2,180) 
Executando o código é criado o seguinte:
Ao fazermos assim ficamos com o problema de colocar as cores… Com um pouco de reflexão chegamos a uma solução, baseada na ideia de separar a circunferência exterior em duas semi-circunferências. O cuidado a ter é com as orientações a cada momento da tartaruga. Aqui basta não esquecer o principio de que a tartaruga quando se movimenta vê sempre o centro à sua esquerda!
def zen11(raio):
    turtle.setheading(90)
    turtle.circle(raio,180)
    turtle.fillcolor('black')
    turtle.begin_fill()
    turtle.circle(raio/2,-180)
    turtle.setheading(270)
    turtle.circle(raio/2,180)
    turtle.circle(raio,-180)
    turtle.end_fill()
ao executar o programa obtemos:
Agora é “só” colocar os círculos pequenos. Uma vez mais basta saber definir os centros:
def zen12(raio):
    turtle.setheading(90)
    turtle.circle(raio,180)
    turtle.fillcolor('black')
    turtle.begin_fill()
    turtle.circle(raio/2,-180)
    turtle.setheading(270)
    turtle.circle(raio/2,180)
    turtle.circle(raio,-180)
    turtle.end_fill()
    # círculos pequenos
    # esquerdo
    turtle.setx(turtle.xcor() + raio/2 -raio/10)
    turtle.fillcolor('white')
    turtle.begin_fill()
    turtle.circle(raio/10)
    turtle.end_fill()
    # direito
    turtle.penup()
    turtle.setx(turtle.xcor()+ raio)
    turtle.pendown()
    turtle.fillcolor('black')
    turtle.begin_fill()
    turtle.circle(raio/10)  
    turtle.end_fill()
    turtle.hideturtle()    
Et voilá!

Mas será que é a única forma de resolver o problema? Será que ficamos satisfeitos??? Olhando para a solução acima o que podemos dizer? Bem, que há muita coisa “fixa”: a posição, a orientação, as cores. Embora estas duas últimas façam parte da definição clássica da imagem, podemos procurar libertar-mo-nos e permitir uma versão mais geral. Por outro lado, e talvez mais importante, o modo como construímos a solução usa componentes de baixo nível e não olha para a imagem como sendo composta por duas que se distinguem … pela posição, orientação e cores!!! Vamos tentar então uma segunda abordagem.
   
def zen(raio,x_cor,y_cor,orient, cor_1,cor_2):
    """
    Desenha uma componente.
    """
    # corpo principal
    vai_para_or(x_cor,y_cor,orient)
    turtle.fillcolor(cor_1)
    turtle.begin_fill()
    turtle.circle(raio,-180)
    #vai_para_or(x_cor,y_cor,orient)   
    turtle.circle(raio/2,-180)
    turtle.setheading(turtle.heading()+180)
    turtle.circle(raio/2,180)
    turtle.end_fill()
    # restaura
    vai_para_or(x_cor,y_cor,orient+90)
    # círculo pequeno  
    turtle.penup()
    turtle.forward(3*raio/2-raio/10)
    turtle.pendown()
    turtle.fillcolor(cor_2)
    turtle.setheading(orient)
    turtle.begin_fill()
    turtle.circle(raio/10)
    turtle.end_fill()
    turtle.hideturtle()
O código apresentado torna mais claro a importância da orientação em cada momento do desenho. O raio do círculo pequeno foi decidido que tenha um valor 10 vezes mais pequeno que o raio da semi-circunferência maior. O resultado quando escolhemos as cores vermelho e azul:
Agora podemos apresentar o nosso programa para o Yin-Yang:
   
def yin_yang(raio,posx,posy):
    zen(raio,posx,posy,90,'black','white')
    zen(raio,posx - 2*raio,posy,270, ‘white','black')
Com esta solução podemos então … brincar:
   
def zen_rose(raio,posx,posy,orientacao,cor_1, cor_2, num):
    for i  in range(num):
        zen(raio,posx,posy,orientacao,cor_1,cor_2)
        orientacao = orientacao + 360/num
Executando vem:
Mas podemos variar um pouco:
def zen_rose_2(raio,posx,posy,orientacao,cor_1, cor_2, num):
    for i  in range(num):
        if i % 2 == 0:
            zen(raio,posx,posy,orientacao,cor_1,cor_2)
        else:
            zen(raio,posx,posy,orientacao,cor_2,cor_1)
        orientacao = orientacao + 360/num
Ou criar objectos mais coloridos:
def zen_rose_tutti(raio,posx,posy,orientacao, num):
    turtle.colormode(255)
    for i  in range(num):
        cor_1 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        cor_2 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        zen(raio,posx,posy,orientacao,cor_1,cor_2)
        orientacao = orientacao + 360/num
E agora:
E como alguém disse: o céu é o limite! Invente!!!
def zen_voa(raio,posx,posy,orientacao, num):
    turtle.speed(10)
    turtle.colormode(255)
    for i  in range(1,num+1):
        cor_1 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        cor_2 = (random.randint(0,255),random.randint(0,255),random.randint(0,255))        
        zen(raio,posx,posy,orientacao,cor_1,cor_2)
        #orientacao = orientacao + 360/num
        posx = posx + i * raio/5
        posy = posy + i * raio/5
 

Formas, Cores e outras coisas....

O módulo turtle da linguagem Python tem um conjunto vasto de operações que podem ser exceptuadas sobre objectos do tipo turtle e sobre um mundo 2D. Durante as aulas vimos alguns exemplos de comandos básicos e o que com eles podíamos fazer. Por exemplo, para desenhar um quadrado:
def quadrado(lado):
    for i in range (4):
        turtle.forward(lado)
        turtle.right(90)
Usando esta definição como componente podemos brincar um pouco. Por exemplo, desenhar um certo número de vezes um quadrado, mudando apenas a orientação:
def tarta_ernesto(posx,posy):
    # tartaruga ernesto
    turtle.shape("turtle")
    turtle.color("yellow")
    turtle.speed(10)
    turtle.pensize(2)
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha
    for i in range(36):
        quadrado(150)
        turtle.right(10)
    turtle.hideturtle()
Como se pode ver existem comandos para:

- definir a forma da tartaruga

definir a cor

definir a velocidade da tartaruga

definir a espessura do rasto

O resultado é ilustrado na figura.
Mudando o ciclo no interior do programa podemos conseguir outro tipo de formas:
def tarta_costa(posx,posy):
    #tartaruga costa
    turtle.shape("turtle")
    turtle.color("blue")
    turtle.speed(10)
    turtle.pensize(2)
    # posiciona
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    # desenha    
    for i in range(400):
        turtle.forward(i)
        turtle.right(91)
    turtle.hideturtle()
Tal como está escrito, a única “coisa” que controlamos e podemos variar é a posição inicial. Visualmente:
Podemos usar a mesma ideia de base (uma forma básicas que é replicada….) para criar diferentes formas coloridas. Experimente você.

quarta-feira, 27 de setembro de 2017

Na aula foi pedido que escrevessem um programa que desenhasse espirais. Ficou claro, após alguma discussão, que o desenho iria depender de dois factores inter-relacionados: o tamanho do traço e o ângulo de viragem. Também é importante definir o número de “voltas” da espiral, claro, mas isso não causa problema de maior.

Daqui resulta um programa simples:
import turtle

def spiro(tam,k,ang,size):
    for i in range(size):
        turtle.forward(tam + k*i)
        turtle.right(ang)
    turtle.hideturtle()
Não é preciso pensarmos muito para perceber que os traços que vamos usar terão que ter o seu tamanho incrementado. Porquê? Bem, porque a não ser assim, em função do ângulo de viragem, em vez de uma espiral teremos repetições da mesma volta. E o ângulo? Se fôr muito pequeno vamos ter uma grande amplitude e dificuldade em girar 360 graus. Se fôr muito grande rapidamente os traços se intersectam. Mas o melhor é experimentar com valores concretos. Experimente tentar obter espirais com o aspecto dos das figuras. E muitas mais. Divirta-se!

sexta-feira, 22 de setembro de 2017

Mais uma vez...

Vamos recomeçar mais uma edição da cadeira de IPRP. Como no passado vou procurar deixar aqui elementos que podem ajudar à melhor compreensão da matéria e consequente melhoria do vosso desempenho.

Estejam atentos!

domingo, 19 de fevereiro de 2017

EXAME RECURSO - P1

Dado o programa
 
def xpto(lista):
    # ordena lista de modo decrescente
    for i in range(len(lista)):
        m = max(lista[i:])
        ind = lista.index(m)
        lista[i],lista[ind] = lista[ind], lista[i]
    return lista
pretende-se saber o que faz, como o faz e se, eventualmente, tem algum erro e como pode ser corrigido.

O programa recebe como entrada uma lista de números e devolve essa mesma lista ordenada de modo decrescente. Funciona percorrendo a lista da esquerda para a direita e na etapa i determinar a posição do maior elemento da sub-lista desde a posição i até ao final. De seguida esse elemento troca a sua posição com o elemento na posição i. Existe uma situação em que este algoritmo não funciona, quando existem elementos repetidos. Para corrigir, basta usar a instrução
 
ind = lista.index(m,i)
no lugar de
 
ind = lista.index(m)

domingo, 8 de janeiro de 2017

Teste 3 - TP2

Problema 2

Saber se temos prémio no euromilhões e o seu valor não é tarefa difícil. Na solução abaixo admitimos que vamos ter a chave correcta e a nossa chave representadas por uma lista de dois elementos. O primeiro, é uma lista de cinco números e, o segundo, uma lista de dois números (as estrelas). Por outro lado o valor dos prémios está armazenado num dicionário em que a chave é um tuplo (n1, n2) que traduz quantos números e estrelas acertámos e o valor é o prémio.
def premio_euro(dicio,chave_certa, minha_chave):
    # as chaves estão na forma [[5 números],[2 estrelas]]
    # verifica números
    num_certos = chave_certa[0]
    num_meus = minha_chave[0]
    conta_n = 0
    for num in num_meus:
        if num in num_certos:
            conta_n += 1
    # verifica estrelas
    est_certas = chave_certa[1]
    est_minhas = minha_chave[1] 
    conta_e = 0
    for est in est_minhas:
        if est in est_certas:
            conta_e += 1
    # calcula prémio
    return dicio.get((conta_n,conta_e),0)
Problema 3

Pretende-se normalizar os números guardados num ficheiro. Cada linha do ficheiro contém um nome e os números. A normalização é feita por linha, subtraindo a cada número a média dos valores dos números da linha e dividindo o resultado pelo respectivo desvio padrão.
import statistics

def normaliza_fich(fich_entrada, fich_saida):
    # abre ficheiros
    f_in = open(fich_entrada,'r',encoding='utf8')
    f_out = open(fich_saida,'w',encoding='utf8')
    # lê e normaliza por linha
    for linha in f_in:
        # recolhe nome e números
        linha = linha.strip().split()
        nome = linha[0]
        numeros = [int(num) for num in linha[1:]]
        # normaliza números
        num_normais = normaliza(numeros)
        # escreve resultado
        nova_linha = nome + ' '.join([str(num) for num in num_normais]) + ‘\n'
        f_out.write(nova_linha)
    # fecha ficheiros
    f_in.close()
    f_out.close()
    
def normaliza(numeros):
    media = statistics.mean(numeros)
    desvio_pad = statistics.stdev(numeros)
    return [(num - media)/desvio_pad for num in numeros]

A função auxiliar normaliza usa o módulo statistics para normalizar os números dados numa lista.

sábado, 7 de janeiro de 2017

Teste 3 - TP1

Problema 2

Num dicionários a diferentes chaves podemos ter associado o mesmo valor. O problema de calcular qual o valor mais frequente pode ser resolvido de forma simples do seguinte modo:
def moda(dicio):
    # inverte dicio
    novo_dicio = {}
    for c,v in dicio.items():
        novo_dicio[v] = novo_dicio.get(v,[]) + [c]
    # passa a lista ordenada
    lista_items = list(novo_dicio.items())
    mais_freq = (0,[])
    for val,lst in lista_items:
        if len(lst) > len(mais_freq[1]):
            mais_freq = (val,lst)
    # devolve o mais valor mais frequente
    return mais_freq    
Como os comentários indicam, a estratégia de solução passa por inverter o dicionário, converter para uma lista e depois calcular o elemento, i.e. o par (valor, lista das chaves com esse valor), mais frequente. Para os pythónicos apresentamos outra solução que recorre a funções anónimas:
def moda_b(dicio):
    # inverte dicio
    novo_dicio = {}
    for c,v in dicio.items():
        novo_dicio[v] = novo_dicio.get(v,[]) + [c]
    # passa a lista ordenada
    lista_items = list(novo_dicio.items())
    lista_items.sort(key=lambda x: len(x[1]),reverse=True)
    # devolve o mais valor mais frequente
    return lista_items[0]
A função anónima (lambda) é usada para o ordenamento ser feito de acordo com o tamanho do elemento na posição 1.

Problema 3

O problema envolvia um ficheiro em que cada linha é formado por um nome e números (>= 3). Pretende-se criar um novo ficheiro com o nome e a média dos números depois de retirar o menor e o maior.

Uma solução mágica:
def fich_media(fich_entrada,fich_saida):
    # modificar linha a linha
    # abre ficheiros
    f_in = open(fich_entrada,'r',encoding='utf8')
    f_out = open(fich_saida,'w',encoding='utf8')
    
    # trata por linha
    for linha in f_in:
        # escolhe numeros
        linha = linha.strip().split()
        nome = linha[0]
        numeros = [ int(num) for num in linha[1:]]
        numeros.sort()
        
        # calcula média
        media = sum(numeros[1:-1])/(len(numeros)-2)
        # escreve nova linha
        f_out.write(nome + str(media) + '\n')
    # fecha ficheiros
    f_in.close()
    f_out.close()
Dada a natureza do enunciado resolvemos natural tratar o problema linha a linha. Cada linha é partida e os seus números ordenados. Calculamos de seguida a média retirando o primeiro (o mais pequeno) e o último (o maior).