sábado, 10 de outubro de 2009

Problema 2.18

Antes de apresentar uma solução possível para este problema preciso fazer um passeio por algo conhecido por autómatos finitos de que irão ouvir falar na cadeira de Teoria da Computação I. De modo simples um autómato finito (AF) é uma máquina de estados. Quando se encontra num dado estado recebe um comando e reage a ele transitando de estado e, eventualmente, realizando alguma acção. Um exemplo concreto, embora redutor, é o caso de um interruptor. Tem associado dois estados de uma lâmpada, acesa e apagada . A transição de estado é comandada pela manipulação do interruptor. Se estiver acesa e eu carregar no interruptor passa para o estado apagada e apaga-se a luz. E inversamente, se estiver no estado apagada e se manipular o interruptor passa para o estado acesa e a luz acende-se. Claro que se estiver acesa e eu der o comando de acender, tudo se mantém nas mesma. E o mesmo para o caso de apagada. Normalmente visualizam-se os autómatos do modo ilustrado pela figura abaixo.





O que é que o nosso problema de pegar numa cadeia formada por palavras separadas por espaços de dimensão arbitrária, que queremos reduzir a apenas um, tem a ver com autómatos finitos?? Bom, se repararmos bem nós temos uma cadeia de caracteres. Podemos partir dela e criar uma outra, semelhante, na qual os espaços a mais foram retirados. É como se colocasse um filtro entre as duas cadeias. O filtro, analisa um caracter da primeira cadeia e decide se o deixa passar ou não. Isso depende de ter visto um caracter e, imediatamente a seguir, um outro. Tal facto é um sinal para se efectuar a filtragem. É como se tivéssemos um autómato finito com dois estados: num, não filtra, no outro filtra. A figura que mostramos a seguir pretende ilustrar a situação.




Foi com base nesta ideia que escrevemos o seguinte programa:



def um_espaco(cad):
"""
Separa as palavras na cadeia deixando-as só com um espaço a separar.
Implementado como um autómato de dois estados.
Se quisermos retirar todos os espaços no início e no fim
fazer primeiro cad = string.strip(cad)
"""
cadeia = " # cadeia vazia
estado = 1
for i in range(len(cad)):
if estado == 1:
if cad[i] == ' ': # espaço em branco!
cadeia = cadeia + ' ' # espaço em branco!
estado = 2
else:
cadeia = cadeia + cad[i]
else: # estado igual a 2
if cad[i] != ' ': # espaço em branco!
cadeia = cadeia + cad[i]
estado = 1
return cadeia


Analisemos o código. Partimos da cadeia cad e vamos construindo progressivamente a cadeia resultado em cadeia, que assim funciona como acumulador. Temos um nome, estado para identificar o estado em que nos encontramos: filtrar ou não. Partimos do estado de não filtrar (linha 9). Com o ciclo for (linha 10) vamos testar todos os caracteres. Depois: se estivermos no estado 1 e lermos um caracter branco , acrescentamos (é o primeiro!!) e transitamos para o estado 2 para continuar a observar (linhas (11 a 14). Se no estado 1 nos aparecer um caracter normal, fazemos uma cópia para a cadeia e continuamos no estado 1 (linha 16); se estivermos no estado 2 (eliminar espaços a mais) e nos aparece um caracter normal, copiamos essa caracter e regressamos ao estado 1 (linhas 18 a 20), caso contrário não fazemos nada. E pronto!

9 comentários:

  1. Professor, partilho uma hipotese alternativa:
    Ao invés de criar um "filtro" que varia com o aparecimento de um espaco, podemos apenas comandar que de um dado conjunto de espacos, apenas o ultimo será copiado, ou seja, apenas o espaco cujo caracter que o siga NAO seja um espaco:

    def blank(frase):
    ..import string
    ..newfrase = ''
    ..frase = string.strip(frase)
    ..for i in range(0,len(frase)):
    ....if (frase[i]==' '):
    ......if (frase[i+1]!=' '):
    ........newfrase=newfrase+frase[i]
    ....else:
    ......newfrase=newfrase+frase[i]
    ..return newfrase

    O unico problema sera que so funciona SEM espaco no fim.. o loop, como esta, nao funciona sem o string.strip, daria erro ao pedir um caracter[i+1] inexistente..

    ResponderEliminar
  2. Creio que seria mais simples usar um ciclo for com somente 1 if.
    **********
    def espaco(frase):
    ppr=''
    frase=string.strip(frase)
    for i in range (len(frase))
    if ((ord(frase[i])) <> 32) or ((ord(frase[i]) == 32) and (ord(frase[i-1]) <> 32)):
    ppr=ppr+frase[i]
    print ppr
    **********
    após aproveitar a boleia do string.strip(),
    cada caracter da cadeia inserida é avaliado segundo o seu valor na tabela ascii: passa se for diferente de 32(espaco) ou, a ser igual, também passa se o caracter antecessor for tudo menos outro espaço.

    Obviamente que, ao invés de utilizar a tabela ascii, poderia fazer uma comparação directa com " ".

    ResponderEliminar
  3. Ora ai esta o problema de nao conhecer o Python (e de nao saber ainda programar, em geral...) !
    Existe, regra geral, sempre uma maneira mais simples de fazer as coisas, que desconheco (regra geral, por funcoes e modulos que desconheco!)

    ResponderEliminar
  4. Tiago(1) e Marco: vejam a resposta que dei noutro post sobre o problema.

    Tiago(2): As soluções são sempre mais do que uma. Por vezes é difícil dizer a que é melhor. Começa pelo significado de "ser melhor": tempo de execução, espaço ocupado pelo programa, elegância, concisão, etc... Mas uma coisa é certa: quanto mais dominarmos as técnicas, independentemente da linguagem melhor. Depois é saber: tenho isto na minha linguagem?

    ResponderEliminar
  5. Creio que existe uma solução ainda (e muito) mais fácil. É a seguinte usando um ciclo while:

    def espacos(cadeia):
    ....while ' ' in cadeia:
    ........cadeia = cadeia.replace(' ',' ')
    ....return cadeia

    O que faz é que enquanto existirem dois espaços seguidos (' '), o programa substitui-os apenas por um (' '). Penso que resulta sempre.

    PS: Eu coloquei 2 espaços seguidos mas penso que ao postar aqui no blog eles passam a 1, mas ideia está lá.

    ResponderEliminar
  6. A solução do Milton é interessante ... mas pouco eficiente computacionalmente.

    ResponderEliminar
  7. Pois, percebo que se houverem muitos espaços, o ciclo while repete-se muitas vezes, muito maior que o ciclo for na sua solução.

    ResponderEliminar
  8. Estou confusa.
    Não chegaria:

    def stuff(cadeia):
    cadeia=string.split(cadeia)
    cadeia=string.join(cadeia)
    return cadeia

    ...?

    ResponderEliminar
  9. Eu não estava confusa. Funciona, claro. Só falta fazer antes o import do módulo string. Ha sempre muitas maneiras de resolver um problema e outras tantas de aprender...

    ResponderEliminar