domingo, 23 de outubro de 2016

Codificar textos

Durante as aulas introduzimos as cadeias de caracteres. Elas são o modo natural de guardar textos. Uma tarefa relevante é a de codificar um texto de modo que possa ser transmitido com segurança. Vamos ver como a tarefa de encriptar um documento pode ser resolvida com facilidade em Python. Iremos usar três métodos diferentes, que naturalmente vão requerer algoritmos distintos. O primeiro, limita-se a separar o texto em duas partes em função da posição par ou ímpar do caractere no texto. Uma vez esta decomposição feita o texto é recomposto juntando as duas partes. O segundo método precisa conhecer o alfabeto usado e estabelece uma correspondência entre cada caractere do alfabeto e um outro caractere do mesmo alfabeto que se encontra n posições à sua frente. Finalmente o terceiro método usa uma correspondência entre caracteres não baseada num distância fixa mas antes definida estocasticamente. Vejamos então algumas soluções possíveis.

Separação pares ímpares

Comecemos com um pedaço de código que deixa claro qual a estratégia a seguir.
1.def encripta_0(texto):
2.    # selecciona pares
3.    # selecciona ímpares
4.    # junta pares e ímpares
5.    pass
Olhemos agora para cada um dos sub-problemas. Como extrair os caracteres que se encontram nas posições pares do texto. Não é preciso pensar muito para decidir que podemos recorrer ao modelo baseado na ideia de ciclo + acumulador:
01.def encripta_0(texto):
02.    # selecciona pares
03.    pares = ''
04.    for i in range(len(texto)):
05.        # índice par?
06.        if i%2 == 0:
07.            pares = pares + texto[i]
08.    # selecciona ímpares
09.    # junta pares e ímpares
10.    pass
Resolver o problema para os mares é semelhante, pelo que chegamos à nossa primeira solução.
01.def encripta_0(texto):
02.    # selecciona pares
03.    pares = ''
04.    for i in range(len(texto)):
05.        # índice par?
06.        if i%2 == 0:
07.            pares = pares + texto[i]
08.    # selecciona ímpares
09.    impares = ''
10.    for i in range(len(texto)):
11.        # índice ímpar?
12.        if i%2 != 0:
13.            impares = impares + texto[i]   
14.    # junta pares e ímpares
15.    novo_texto = pares + impares
16.    return novo_texto
Porque é que esta solução não nos agrada? Desde logo porque o texto é percorrido duas vezes. Isso decorre do facto de termos isolado a construção dos pares da dos ímpares. Mas é óbvio que podemos fazer a divisão par/ímpar percorrendo o texto uma só vez:
01.def encripta_1(texto):
02.    pares = ''
03.    impares = ''
04.    for i in range(len(texto)):
05.        if i % 2 == 0:
06.            pares = pares + texto[i]
07.        else:
08.            impares = impares + texto[i]
09.    return pares + impares
A solução pode ser ainda outra se usarmos o conhecimento que temos da operação de fatiamento:
1.def encripta_11(texto):
2.    pares = texto[0::2]
3.    impares = texto[1::2]
4.    return pares + impares
ou ainda, eliminando os nomes auxiliares:
1.def encripta_12(texto):
2.    return texto[0::2] + texto[1::2]
Distância Fixa

Nesta abordagem vamos necessitar conhecer o alfabeto, ou seja, os símbolos que podem aparecer no texto. Para simplificar vamos supor que o nosso alfabeto é formado pelas letras minúsculas mais o espaço em branco. O modelo ciclo - acumulador permite um primeiro esboço de solução:
1.def codifica(texto,n):
2.    alfabeto = 'abcdefghijklmnopqrstuvwxyz '
3.    novo_texto = ''
4.    for car in texto:
5.        # por cada caractere do texto procura o equivalente n posições “à frente”
6.        # junta ao acumulador
7.    return novo_texto
O sub-problema realmente diferente consiste em encontrar o novo caractere. Um ideia básica é usar o seu índice no alfabeto e somar-lhe n. O único problema que pode surgir é com os caracteres no final. Por exemplo, caso n=2 o equivalente a z deve ser o a. Podemos resolver esta questão usando a operação módulo:
1.def codifica(texto,n):
2.    alfabeto = 'abcdefghijklmnopqrstuvwxyz '
3.    novo_texto = ''
4.    for car in texto:
5.        novo_indice = (alfabeto.index(car) + n) % len(alfabeto)
6.        novo_car = alfabeto[novo_indice]
7.        novo_texto = novo_texto + novo_car
8.    return novo_texto
É claro que podemos chegar a outra solução. Por exemplo, podemos construir primeiro a correspondência e só depois encriptar.
01.def codifica_2(texto,n):
02.    alfabeto = 'abcdefghijklmnopqrstuvxyz '
03.    # constrói código
04.    cod_alfabeto = ''
05.    for car in alfabeto:
06.        indice = alfabeto.index(car)
07.        novo_indice = (indice + n) % len(alfabeto)
08.        cod_alfabeto = cod_alfabeto + alfabeto[novo_indice]
09.         
10.    # encripta
11.    novo_texto = ''
12.    for car in texto:
13.        ind = alfabeto.index(car)
14.        novo_texto = novo_texto + cod_alfabeto[ind]
15.    return novo_texto
Faz ainda sentido que o alfabeto seja um parâmetro do programa principal:
01.def codifica_3(texto,alfabeto, n):
02.    # define chave
03.    alfa_chave = chave(alfabeto,n)
04.    # encripta
05.    novo_texto = ''
06.    for car in texto:
07.        ind = alfabeto.index(car)
08.        novo_texto = novo_texto + alfa_chave[ind]
09.    return novo_texto
10. 
11.def chave(alfabeto,n):
12.    alfa_chave = ''
13.    for car in alfabeto:
14.        indice = alfabeto.index(car)
15.        novo_indice = (indice + n) % len(alfabeto)
16.        alfa_chave = alfa_chave + alfabeto[novo_indice]
17.    return chave
Notar que criámos uma definição auxiliar para a construção da chave. Chave Aleatória

A ideia agora é estabelecer uma relação um para um entre o alfabeto e a chave de modo aleatório. Na linha do último exemplo vamos ver como podemos criar a chave. Em Python existem várias formas de o poder fazer, umas bem simples, mas vamos buscar uma solução à luz do que foi dados nas aulas. Vamos então caçar com gato…. A ideia é trivial: gerar uma permutação do alfabeto inicial recorrendo a uma cópia desse alfabeto ao qual vamos buscar aleatoriamente um símbolo para a chave. De seguida esse símbolo é retirado para garantir que geramos uma permutação.
01.import random
02. 
03.def define_chave(alfabeto):
04.    simbolos = alfabeto
05.    chave = ''
06.    for car in alfabeto:
07.        novo_simb = random.choice(simbolos)
08.        ind = simbolos.index(novo_simb)
09.        simbolos = simbolos[:ind] + simbolos[ind+1:]
10.        chave = chave + novo_simb   
11.    return chave
Tendo a chave, a encriptação é semelhante ao exemplo anterior.
1.def encrita_3(texto, alfabeto):
2.    chave = define_chave(alfabeto)
3.    novo_texto = ''
4.    for car in texto:
5.        ind = alfabeto.index(car)
6.        novo_texto = novo_texto + chave[ind]   
7.    return novo_texto

Sem comentários:

Enviar um comentário