terça-feira, 29 de dezembro de 2009

Errar é humano...

Um aluno de IPRP estava com dificuldades num problema que saiu no exame de 2006 e pediu-me ajuda. O problema era o seguinte:

Suponha que tem uma cadeia de caracteres com um dado comprimento e a pretende dividir em n partes iguais. Implemente em Python o respectivo programa não se esquecendo de prever o caso de o comprimento da cadeia não ser múltiplo de n.

Enviou-me a sua tentativa de solução que não funcionava:
01.def divide(f, p):
02.    aux=[]
03.    a=len(f)
04.    lpp=(len(f)-1)/p
05.    dif=0
06.    x=lpp
07.    faux=''
08.    n=0
09.    while(n < p):
10.        faux=f[dif:lpp+1]
11.        aux.append(faux)
12.        dif=(dif+lpp)+1
13.        lpp=(lpp+x)       
14.        n=n+1
15.    return aux

Qual é a lógica desta solução? Bem andar a partir a frase em pedaços todos do mesmo comprimento. Calcula-se esse comprimento na linha 4. Usam-se duas marcas (dif e lpp) para determinar os limites de cada pedaço, que vão sendo guardados em aux, a capa passagem pelo ciclo while. A ideia faz sentido.


Vamos ver alguns dos seus problemas. Começo pelo código inútil:

a = len(f), não está lá a fazer nada, nunca sendo usado.
faux = '' : esta inicialização não é preciso para nada

Agora os erros:

lpp = (len(f) - 1) / p: não se deve retirar um ao comprimento, dado o modo como a divisão funciona.
faux[f[dif:lpp+1]: errado devido à soma de 1.
dif = dif + lpp +1: aqui talvez o erro maior! Em cada etapa dif deve avançar para o valor de lpp.

Posto tudo isto uma solução usando esta ideia seria (notem que uso x no lugar de lpp, por uma questão de clareza):
01.def divide_corrigido(f, p):
02.   aux=[]
03.   lpp=len(f)/p
04.   dif=0
05.   x = lpp
06.   n=0
07.   while(n < p):
08.       faux=f[dif:x]
09.       aux.append(faux)
10.       dif=x
11.       x = x + lpp
12.       n=n+1
13.   aux.append(f[dif:])
14.   return aux

Mas não estou satisfeito, pois podemos encontrar uma solução mais elegante, tendo por base o mesmo princípio: duas marcas que vão avançando a cada passagem pelo ciclo. Mas para tal podemos usar uma combinação de um ciclo for com a operação range:
01.def divide_ec(frase,n):
02.   """divide a frase em n pedaços iguais."""
03.   # número de caracteres em cada parte
04.   num_car = len(frase)/n
05.   # calcula e guarda em aux
06.   aux = []
07.   for i in range(0,len(frase),num_car):
08.       aux.append(frase[i:i+num_car])
09.   return aux

O trabalho de fazer avançar a marca correctamente é assegurado pelo modo de funcionar do range comn três argumentos. Concordarão que este programa é mais claro do que o proposto! E prevê o caso de a frase ter um comprimento que não é múltiplo de n!

Se gostarem de listas por compreensão podem ainda prescindir de aux.:
1.def divide_ec_2(frase,n):
2.   num_car = len(frase)/n
3.   return [frase[i:i+num_car] for i in range(0,len(frase),num_car)]


Mas voltemos ao enunciado, que nos fala em n partes iguais. Se corrermos o programa, verificamos que nos casos em que não é múltiplo existem n+1 partes! Como corrigir este aspecto? Uma solução será esticar a cadeia e passar a ter cadeias todas do mesmo tamanho, menos a última. Esta era, aliás, a interpretação correcta do enunciado...

1.def divide_ec_3(frase,n):
2. """divide uma sequência frase em n partes.
3. A última pode ser menor."""
4. parte=(len(frase) + n - 1)/n
5. aux = []
6. for i in range(0,n):
7.  aux.append(frase[parte*i:parte*(i+1)])
8. return aux


E até nos podemos dar ao luxo de ter uma solução recursiva!!
1.def divide_ec_4(frase,n):
2. if n == 0:
3.  return []
4. else:
5.  return [frase[:len(frase)/n]] + divide_ec_4(frase[len(frase)/n:],n-1)


E assim terminamos este exercício. aprendemos muito com os nossos erros ... e com os erros dos outros!!

1 comentário:

  1. Bom post. Pessoalmente tb gosto de analisar código usando diversos algoritmos e até paradigmas diferentes. Muitas vezes acabamos melhorando bastante o processo com isso.
    Só uma correção, na chamada recursiva de divide() no return deve ser divide_ec_4().
    Abraço.

    ResponderEliminar