web-dev-qa-db-fra.com

Fractionner une chaîne en mots et en ponctuation

J'essaie de scinder une chaîne en mots et en ponctuation, en ajoutant la ponctuation à la liste produite par la scission.

Par exemple:

>>> c = "help, me"
>>> print c.split()
['help,', 'me']

Ce que je veux vraiment que la liste ressemble à ceci est:

['help', ',', 'me']

Donc, je veux que la chaîne soit scindée en blanc avec la ponctuation divisée par les mots.

J'ai d'abord essayé d'analyser la chaîne, puis d'exécuter la scission:

>>> for character in c:
...     if character in ".,;!?":
...             outputCharacter = " %s" % character
...     else:
...             outputCharacter = character
...     separatedPunctuation += outputCharacter
>>> print separatedPunctuation
help , me
>>> print separatedPunctuation.split()
['help', ',', 'me']

Cela produit le résultat que je veux, mais est terriblement lent pour les gros fichiers.

Y a-t-il un moyen de le faire plus efficacement?

52
David A

C'est plus ou moins la façon de le faire:

>>> import re
>>> re.findall(r"[\w']+|[.,!?;]", "Hello, I'm a string!")
['Hello', ',', "I'm", 'a', 'string', '!']

L'astuce consiste à ne pas chercher à savoir où scinder la chaîne, mais à inclure dans les jetons.

Mises en garde:

  • Le trait de soulignement (_) est considéré comme un caractère interne au mot. Remplacez\w, si vous ne le souhaitez pas.
  • Cela ne fonctionnera pas avec les guillemets (simples) dans la chaîne.
  • Placez les signes de ponctuation supplémentaires que vous souhaitez utiliser dans la moitié droite de l'expression régulière.
  • Tout ce qui n'est pas explicitement mentionné dans le dossier est supprimé en silence.
73
user3850

Voici une version compatible avec Unicode:

re.findall(r"\w+|[^\w\s]", text, re.UNICODE)

La première alternative saisit des séquences de caractères Word (comme défini par unicode, afin que "résumé" ne se transforme pas en ['r', 'sum']); la seconde attrape des caractères individuels non-Word, en ignorant les espaces.

Notez que, contrairement à la réponse principale, le guillemet simple est traité comme une ponctuation distincte (par exemple, "Je suis" -> ['I', "'", 'm']). Cela semble être standard dans la PNL, donc je considère que c'est une fonctionnalité.

30
LaC

Dans la syntaxe d'expression régulière de style Perl, \b correspond à une limite de Word. Cela devrait être pratique pour faire une scission basée sur la regex.

edit: J'ai été informé par hop que les "correspondances vides" ne fonctionnent pas dans la fonction split du module re de Python. Je laisserai ceci ici comme information pour quiconque se laisserait dérouter par cette "fonctionnalité".

5
Svante

Voici mon entrée.

J'ai des doutes sur l'efficacité de cette opération, ou sur le fait que tous les cas sont résolus (notez le "!!!" regroupé; cela peut être une bonne chose ou non).

>>> import re
>>> import string
>>> s = "Helo, my name is Joe! and i live!!! in a button; factory:"
>>> l = [item for item in map(string.strip, re.split("(\W+)", s)) if len(item) > 0]
>>> l
['Helo', ',', 'my', 'name', 'is', 'Joe', '!', 'and', 'i', 'live', '!!!', 'in', 'a', 'button', ';', 'factory', ':']
>>>

Une optimisation évidente consisterait à compiler la regex à l’avance (à l’aide de re.compile) si vous envisagez de le faire ligne par ligne. 

3
Chris Cameron

Voici une mise à jour mineure de votre implémentation. Si vous essayez de faire quelque chose de plus détaillé, je vous suggère d’examiner le NLTK.

Cela pourrait être un peu plus rapide puisque '' .join () est utilisé à la place de + =, qui est plus rapide .

import string

d = "Hello, I'm a string!"

result = []
Word = ''

for char in d:
    if char not in string.whitespace:
        if char not in string.ascii_letters + "'":
            if Word:
                    result.append(Word)
            result.append(char)
            Word = ''
        else:
            Word = ''.join([Word,char])

    else:
        if Word:
            result.append(Word)
            Word = ''
print result
['Hello', ',', "I'm", 'a', 'string', '!']
1
monkut

Je pense que vous pouvez trouver toute l'aide que vous pouvez imaginer dans NLTK , surtout que vous utilisez python. Il y a une bonne discussion complète sur cette question dans le tutoriel.

0
dkretz

Essaye ça:

string_big = "One of Python's coolest features is the string format operator  This operator is unique to strings"
my_list =[]
x = len(string_big)
poistion_ofspace = 0
while poistion_ofspace < x:
    for i in range(poistion_ofspace,x):
        if string_big[i] == ' ':
            break
        else:
            continue
    print string_big[poistion_ofspace:(i+1)]
    my_list.append(string_big[poistion_ofspace:(i+1)])
    poistion_ofspace = i+1

print my_list
0
Siddharth Sonone

Si vous allez travailler en anglais (ou dans une autre langue courante), vous pouvez utiliser NLTK (il existe de nombreux autres outils pour le faire, tels que FreeLing ).

import nltk
sentence = "help, me"
nltk.Word_tokenize(sentence)
0

Je suis arrivé avec un moyen de tokenize tous les mots et les modèles \W+ en utilisant \b qui n'a pas besoin de codage en dur:

>>> import re
>>> sentence = 'Hello, world!'
>>> tokens = [t.strip() for t in re.findall(r'\b.*?\S.*?(?:\b|$)', sentence)]
['Hello', ',', 'world', '!']

Ici, .*?\S.*? est un motif correspondant à tout ce qui n’est pas un espace et $ est ajouté pour correspondre au dernier jeton d’une chaîne s’il s’agit d’un symbole de ponctuation.

Notez cependant que ceci va regrouper les signes de ponctuation composés de plusieurs symboles:

>>> print [t.strip() for t in re.findall(r'\b.*?\S.*?(?:\b|$)', '"Oh no", she said')]
['Oh', 'no', '",', 'she', 'said']

Bien sûr, vous pouvez trouver et scinder ces groupes avec:

>>> for token in [t.strip() for t in re.findall(r'\b.*?\S.*?(?:\b|$)', '"You can", she said')]:
...     print re.findall(r'(?:\w+|\W)', token)

['You']
['can']
['"', ',']
['she']
['said']
0
FrauHahnhen