blank

Python: Expressão regular que reconheça palavras na língua portuguesa

Python: Expressão regular que reconheça palavras na língua portuguesa

17 de fevereiro de 2020 0 Por Ramos de Souza Janones
Powered by Rock Convert

Existem algumas alternativas em Python para Expressão regular que reconheça palavras na língua portuguesa. Uma alternativa é usar o atalho \w, que em Python 3, por padrão, já pega letras acentuadas:

from re import findall
def frequency(string):
    palavras = findall(r"\w+", string)
    dicionario = {}
    for w in palavras:
        if w in dicionario:
            dicionario[w] += 1
        else:
            dicionario[w] = 1
    return dicionario


string = "Eu desconfio que houve uma sabotagem, exatamente para manchar a gestão eficiente que está sendo feita na Cedae, preparando ela para o leilão"
print(frequency(string))

Se bem que \w é muito abrangente, pegando inclusive letras de outros alfabetos (como o japonês, árabe, cirílico, etc), além de pegar números e o caractere _ (ou seja, vai considerar que “123” e “a_b” são palavras).

Se quiser somente as letras, pode usar:

palavras = findall(r"[^\W\d_]+", string)

Esta é uma classe de caracteres negados, e pega tudo que não está entre [^ e ]. No caso, temos \W (tudo que não for \w), \d (números) e o próprio caractere _. Ou seja, ele pega somente as letras que \w já pega, ignorando os números e _.

Para ambos os casos acima, a saída é:

{'Eu': 1, 'desconfio': 1, 'que': 2, 'houve': 1, 'uma': 1, 'sabotagem': 1, 'exatamente': 1, 'para': 2, 'manchar': 1, 'a': 1, 'gestão': 1, 'eficiente': 1, 'está': 1, 'sendo': 1, 'feita': 1, 'na': 1, 'Cedae': 1, 'preparando': 1, 'ela': 1, 'o': 1, 'leilão': 1}

Detalhe: normalização Unicode no Python

Um adendo, teste com esta string (copie e cole em vez de digitar diretamente):

Leia também:  
string = "leilão leilão"
print(frequency(string))

O resultado será:

{'leilão': 1, 'leila': 1, 'o': 1}

Duvida? Veja aqui

Isso acontece porque uma das palavras “leilão” está normalizada para a forma NFD. Para mais detalhes sobre o que é isso, sugiro dar uma lida aqui e aqui, mas basicamente o Unicode define que algumas letras acentuadas podem ter mais de uma forma de serem representadas, sendo que na forma NFD, letras como ã são decompostas em 2 caracteres (o a e o ~).

Por isso a regex não consegue mais detectar que é tudo uma palavra só, já que o ~ não é uma letra, nem número, nem _, por isso o \w ignora este caractere.

Uma solução para este caso seria normalizar para NFC (assim os 2 caracteres são “unidos” em um só, ou seja, o a e o ã se tornam o ã, que a regex consegue detectar):

from re import findall
import unicodedata as uc

def frequency(string):
    # normalizar para NFC
    palavras = findall(r"[^\W\d_]+", uc.normalize('NFC', string))
    # o resto da função é igual

Não ficou claro de onde vem as strings que a função irá analisar, mas o fato é que é uma possibilidade que elas estejam em NFD (e como você pode ver pelo exemplo acima, visualmente não é possível saber; somente quando o programa for manipular a string é que isso será detectado, podendo dar diferença se não for tratado).


Se você quer se limitar a somente palavras da língua portuguesa, pode ainda fazer:

palavras = findall(r"[a-záéíóúçâêôãõà]+", uc.normalize('NFC', string), re.I)

Eu incluí os acentos e a cedilha (nunca vi uma palavra com acento circunflexo no i, por isso não coloquei, mas se for o caso é só adicionar). Também usei a flag re.I para que a regex considere tanto letras maiúsculas quanto minúsculas (assim eu não preciso colocar A-ZÁÉÍ... na regex).

Como vender Software - Seja desktop, web ou MobilePowered by Rock Convert

Vale lembrar ainda que na língua portuguesa existem palavras compostas, então o hífen deve ser incluído na regex (mas este deve ter pelo menos algumas letras antes e depois). Uma alternativa seria:

palavras = findall(r"[a-záéíóúçâêôãõà]+(?:-[a-záéíóúçâêôãõà]+)*", uc.normalize('NFC', string), re.I)

Assim, temos uma sequência de letras (igual ao exemplo anterior), seguido de “hífen + letras”, sendo que esta sequência “hífen + letras” pode ocorrer zero ou mais vezes (indicado pelo quantificador *). Os parênteses usam a sintaxe (?: para que este seja um grupo de não-captura (sem o ?:, isto seria um grupo de captura e a documentação diz que findall retorna somente os grupos quando estes estão presentes – ou seja, retornaria somente o trecho que foi pego pelos parênteses; usando um grupo de não captura eu garanto que todos os matches são retornados).

Assim, a string pode ter palavras como “beija-flor” e “pão-de-ló”, que elas serão contabilizadas como se fossem uma só (usando as regex anteriores, “beija” e “flor” seriam consideradas palavras separadas).


Por fim, que as letras depois do apóstrofo devem ser ignoradas), uma alternativa é:

palavras = findall(r"(?<!')[a-záéíóúçâêôãõà]+(?:-[a-záéíóúçâêôãõà]+)*", uc.normalize('NFC', string), re.I)

Agora eu uso um negative lookbehind (o trecho (?<!')) que verifica se antes das letras não tem um apóstrofo. Assim, ele ignora o “‘s” em “Coleridge’s” (considerando que apenas “Coleridge” é uma palavra, pois o “s” não será contabilizado e sequer aparecerá nos resultados). Lembrando que sem o lookbehind, o “s” é contabilizado como uma palavra separada.


Mas se quiser que “Coleridge’s” seja uma única palavra, basta usar:

palavras = findall(r"[a-záéíóúçâêôãõà]+(?:[-'][a-záéíóúçâêôãõà]+)*", uc.normalize('NFC', string), re.I)

A diferença é que agora eu uso [-'] (um hífen ou apóstrofo) para marcar as palavras “compostas”.

Evidentemente que você pode trocar [a-záéí...] por \w ou [^\W\d_].


A desvantagem destas expressões para palavras compostas é que você tem que repetir o trecho que corresponde à letra, mas isso é para garantir que não haverá palavras que começam ou terminam com ' ou hífen. Mas isso pode ser contornado assim:

letras = r"[a-záéíóúçâêôãõà]+"
palavras = findall(f"{letras}(?:[-']{letras})*", uc.normalize('NFC', string), re.I)

Assim, você só precisa mudar a definição de “letra” uma vez.


Por fim – não diretamente relacionado à regex – você também poderia montar o resultado assim:

for w in palavras:
    dicionario[w] = dicionario.get(w, ) + 1

Pois dicionários possuem o método get que pode opcionalmente retornar um valor default caso a chave não exista (no caso, se a chave w não existir, retorna zero).

Ou você pode usar um Counter, que serve justamente para o que você precisa no Python:

from re import findall
import unicodedata as uc
from collections import Counter

def frequency(string):
    letras = r"[a-záéíóúçâêôãõà]+"
    palavras = findall(f"{letras}(?:[-']{letras})*", uc.normalize('NFC', string), re.I)
    return Counter(palavras)
Powered by Rock Convert
Siga os bons!
Últimos posts por Ramos de Souza Janones (exibir todos)
vote
Article Rating

LEIA TAMBÉM:  Começando a programar em Python – Parte 2