terça-feira, 1 de novembro de 2016

Exercícios Complementares e Programação

Durante a semana passada andámos a resolver exercícios complementares envolvendo cadeias de caracteres e tuplos. Esses exercícios permitiram consolidar o conhecimento sobre os objectos de cada um destes tipos e respectivas operações (na realidade, uma parte delas). Vamos apresentar soluções para esses exercícios aproveitando a ocasião para, nalguns casos mais complexos, ilustrar formas de dominar a complexidade.

Comecemos pelos dois primeiros (retirar duplicados de uma cadeia de caracteres, efectuar a diferença entre duas cadeias de caracteres) e pelo exercício do produto escalar.
01.def retira_dup(cadeia):
02.    nova_cad = ''
03.    for i in range(len(cadeia)):
04.        if cadeia[i] not in cadeia[i+1:]:
05.            nova_cad += cadeia[i]      
06.    return nova_cad
07. 
08.def dif_cad(cad1,cad2):
09.    cad3 = ''
10.    for car in cad2:
11.        if car not in cad1:
12.            cad3 += car
13.    return cad3
14. 
15.def prod_esc(vec1,vec2):
16.    soma = 0
17.    for i in range(len(vec1)):
18.        soma += vec1[i] * vec2[i]
19.    return soma
Estes três casos são instâncias de um padrão de programação simples, designado por ciclo-acumulador. O acumulador pode ser de um dado tipo (cadeia de caracteres, inteiros) e vamos acrescentando ao acumulador mais um elemento, eventualmente condicionado a um teste (dois primeiros exemplos.

Mesmo com estes exemplos triviais podemos propor implementações alternativas:
01.def retira(cadeia):
02.    nova_cad = ''
03.    for car in cadeia:
04.        if car not in nova_cad:
05.            nova_cad += ca
06.    return nova_cad
07. 
08.def prod_esc(v1,v2):
09.    comp = min(len(v1), len(v2))
10.    soma = 0
11.    for i in range(comp):
12.        soma +=  v1[i] * v2[i]
13.    return soma
14. 
15. 
16. 
17.def prod_esc_2(vec1,vec2):
18.    comp = min(len(vec1),len(vec2))
19.    aux = tuple()
20.    for i in range(comp):
21.        aux += (vec1[i] * vec2[i],)
22.    return sum(aux)
No primeiro caso, a travessia da cadeia é feita de modo diferente (por conteúdo). No segundo caso, o programa passa a funcionar correctamente mesmo quando os vectores têm dimensões diferentes. No terceiro exemplo, usamos um tuplo para ir armazenando os produtos internos que no final são somados.

Há muitos exemplos desta forma. Por exemplo, criar uma lista só com o elementos não nulos de outra lista:
1.def so_posit(vec):
2.    vec_posit = ()
3.    for val in vec:
4.        if val > 0:
5.            vec_posit += (val,)
6.    return vec_posit
Um outro exercício pede-nos para retirar espaços entre palavras num texto guardado como uma (eventualmente longa) cadeia de caracteres. Este problema, aparentemente simples tem algumas dificuldades escondidas. Uma ideia simples consiste em detectar um espaço em branco e enquanto existirem espaços em brancos não fazer nada, até ao momento que volta a aparecer um espaço em branco. Esta solução pode ser implementada recorrendo a um ciclo diferente do ciclo for a que estamos habituados. Trata-se do ciclo while, que é executado enquanto uma dada condição for verificada. Vejamos então o código:
01.def retira_espacos_1(cadeia):
02.    nova_cadeia = ''
03.    dim = len(cadeia)
04.    i = 0
05.    while i < dim:
06.        nova_cadeia += cadeia[i]
07.        if cadeia[i] == ' ':
08.            while i < dim and cadeia[i] == ' ':
09.                i += 1
10.        else:
11.            i += 1     
12.    return nova_cadeia
Não se pense que não se pode aplicar uma solução com ciclos for. Vamos mostrar uma solução possível que faz aparecer um elemento que em inglês se designa por flag. Trata-se de um booleano que nos permite separar duas situações (caracteres brancos ou não). Na solução apresentada mais em baixo também generalizámos para o conceito de separador incluir um caractere de tabulação (\t).
01.def retira_espacos_2(cadeia):
02.    sep = ' \t'
03.    nova_cadeia = ''
04.    branco = False
05.    for car in cadeia:
06.        if car in sep and not branco:
07.            branco = True
08.            nova_cadeia += ' '
09.        elif car not in sep:
10.            nova_cadeia += car
11.            branco = False
12.    return nova_cadeia
O problema de obter a transposta de uma matriz pode ser resolvido facilmente no pressuposto de que a matriz está representada como um tuplo de tuplos sendo que cada elemento representa uma linha da matriz. A ideia é percorrer a matriz de modo não natural , por colunas, transformando cada coluna numa … linha!
01.def transpose(matrix):
02.    res = ()
03.    for j in range(len(matrix[0])):
04.        # por cada coluna
05.        line = ()
06.        for i in range(len(matrix)):
07.            # constrói nova linha
08.            line += (matrix[i][j],)
09.        # junta linha
10.        res  += (line,)
11.    return res
Sabendo como obter a transposta de uma matriz põe ajudar-nos a resolver o problema do quadrado mágico: verificar se todas as linhas, colunas e diagonais têm somas iguais ao valor do número mágico. Numa primeira abordagem a solução pode ser:
1.def magico(matriz):
2.    # verifica linhas
3.    # verifica colunas
4.    # verifica diagonais
5.    pass
Vamos tentar resolver cada sub-problema de modo isolado. começamos pelo mais fácil: as linhas.
01.def magico(matriz):
02.    tam = len(matriz)
03.    numero = sum(matriz[0])
04.    # verifica linhas
05.    for i in matriz:
06.        soma = sum(i)
07.        if soma != numero:
08.            return False, None
09. 
10.    # verifica colunas
11.    # verifica diagonais
No caso das colunas a questão é mais difícil pois não existe modo de percorrer de forma elementar as colunas. A menos que transformemos o problema de modo a que fique idêntico ao anterior, transformando as colunas em linhas. Mas isso é o que conseguimos com a operação transposta!!!
01.def magico(matriz):
02.    tam = len(matriz)
03.    numero = sum(matriz[0])
04.    # verifica linhas
05.    for i in matriz:
06.        soma = sum(i)
07.        if soma != numero:
08.            return False, None
09. 
10.    # verifica colunas
11.    for i in transpose(matriz):
12.        soma = sum(i)
13.        if numero != soma:
14.            return False, None
15. 
16.    # verifica diagonais
Fica o problema das diagonais. São duas: a principal e a secundária. Os elementos da primeira têm os índices todos iguais. Quanto à segunda, os índices somam a dimensão da matriz mais um. Isto é assim porque o segundo índice varia de 1 a n o primeiro varia de n a 1! Daí a solução final:
01.def magico(matriz):
02.    tam = len(matriz)
03.    numero = sum(matriz[0])
04.    # verifica linhas
05.    for i in matriz:
06.        soma = sum(i)
07.        if soma != numero:
08.            return False, None
09. 
10.    # verifica colunas
11.    for i in transpose(matriz):
12.        soma = sum(i)
13.        if numero != soma:
14.            return False, None
15. 
16.    # verifica diagonais
17.    soma = 0
18.    for i in range(tam):
19.        soma = soma + matriz[i][i]
20.    if numero != soma:
21.        return False, None
22.    soma = 0
23.    for i in range(tam):
24.        soma = soma + matriz[tam-i-1][i]
25.    if numero != soma:
26.        return False, None
Este exercício mostra, ainda que seja de modo primitivo, a ideia de dividir um problema em sub-problemas, teoricamente mais simples. Vejamos a mesma ideia num último exemplo que envolve um programa que conta o número de caracteres, palavras e linhas existentes num texto.
01.def wc_1(texto):
02.    if texto:
03.        # conta caracteres
04.        num_car = conta_car(texto)
05.        # conta palavras
06.        num_pal = conta_pal(texto)
07.        # conta linhas
08.        num_lin = conta_linhas(texto)
09.        return num_car, num_pal, num_lin
10.    else:
11.        return 0,0,0
Num texto não vazio, contamos separadamente cada condição. Faltam as funções que resolvem cada um dos casos:
01.def conta_car(texto):
02.    return len(texto)
03. 
04.def conta_linhas(texto):
05.    return texto.count(‘\n’) + 1
06. 
07. 
08.def conta_pal(texto):
09.    sep_pal = '\n\t '
10.    pal = False
11.    num_pal = 0
12.    for car in texto:
13.        if (car not in sep_pal) and not pal:
14.            num_pal += 1
15.            pal = True
16.        elif car in sep_pal and pal:
17.            pal = False
18.    return num_pal
Também aqui as duas primeiras situações são triviais e apenas a contagem de palavras levanta algumas dificuldades que resolvemos recorrendo à técnica da flag já acima referida. É evidente que esta solução não é muito eficiente pois o texto é percorrido mais do que uma vez. Mas se entendeu a solução que apresentámos não teria dificuldade em mudar para aquela que a seguir apresentamos e que já não sofre da ineficiência referida:
01.def wc_2(texto):
02.    blank = ' \n\t'
03.    if len(texto) == 0:
04.        return 0,0,0
05.    cars = 0
06.    palavras = 1
07.    linhas = 1
08. 
09.     
10.    for i in range(len(texto)-1):
11.        cars+= 1
12.        if texto[i] in blank and texto[i+1] not in blank:
13.            palavras += 1
14.             
15.        if texto[i] == '\n':
16.            linhas += 1
17.             
18.    return cars, palavras, linhas

Sem comentários:

Enviar um comentário