Question Séparer les chaînes avec plusieurs délimiteurs?


Je pense que ce que je veux faire est une tâche assez courante mais je n'ai trouvé aucune référence sur le web. J'ai du texte, avec ponctuation, et je veux la liste des mots.

"Hey, you - what are you doing here!?"

devrait être

['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

Mais Python str.split() ne fonctionne qu'avec un seul argument ... J'ai donc tous les mots avec la ponctuation après que je les ai séparés avec des espaces. Des idées?


506
2018-06-29 17:49


origine


Réponses:


Un cas où les expressions régulières sont justifiées:

import re
DATA = "Hey, you - what are you doing here!?"
print re.findall(r"[\w']+", DATA)
# Prints ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

373
2018-06-29 17:56



re.split ()

re.split (pattern, string [, maxsplit = 0])

Séparer la chaîne par les occurrences du modèle. Si les parenthèses de capture sont utilisées avec un motif, le texte de tous les groupes du motif est également renvoyé dans la liste résultante. Si maxsplit est différent de zéro, des fractionnements au maximum de maxsplit se produisent et le reste de la chaîne est renvoyé comme élément final de la liste. (Note d'incompatibilité: dans la version d'origine de Python 1.5, maxsplit était ignoré. Ce problème a été corrigé dans les versions ultérieures.)

>>> re.split('\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split('(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split('\W+', 'Words, words, words.', 1)
['Words', 'words, words.']

421
2018-06-29 17:57



Un autre moyen rapide de le faire sans regexp est de remplacer les caractères en premier, comme ci-dessous:

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']

262
2017-08-27 16:10



Tant de réponses, pourtant je ne trouve aucune solution qui fasse efficacement ce que le Titre des questions demandent littéralement (diviser sur plusieurs séparateurs possibles - au contraire, de nombreuses réponses suppriment tout ce qui n’est pas un mot, ce qui est différent). Donc, voici une réponse à la question dans le titre, qui repose sur la norme Python et efficace re module:

>>> import re  # Will be splitting on: , <space> - ! ? :
>>> filter(None, re.split("[, \-!?:]+", "Hey, you - what are you doing here!?"))
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

où:

  • la […] allumettes un des séparateurs répertoriés à l'intérieur,
  • la \- dans l'expression régulière est là pour empêcher l'interprétation spéciale de - comme indicateur de plage de caractères (comme dans A-Z),
  • la + saute un ou plus délimiteurs (il pourrait être omis grâce à la filter(), mais cela produirait inutilement des chaînes vides entre les séparateurs correspondants), et
  • filter(None, …) supprime les chaînes vides éventuellement créées par les séparateurs avant et arrière (puisque les chaînes vides ont une valeur booléenne fausse).

Ce re.split() précisément "scissions avec séparateurs multiples", comme demandé dans le titre de la question.

Cette solution ne souffre pas non plus de problèmes avec les caractères non-ASCII dans les mots (voir le premier commentaire à La réponse de ghostdog74).

le re module est beaucoup plus efficace que de faire des boucles Python et teste "à la main".


203
2018-05-18 09:43



Une autre façon, sans regex

import string
punc = string.punctuation
thestring = "Hey, you - what are you doing here!?"
s = list(thestring)
''.join([o for o in s if not o in punc]).split()

47
2017-07-21 06:02



Pro-Tip: Utilisation string.translate pour les opérations de chaîne les plus rapides que Python a.

Une preuve ...

Tout d'abord, la façon lente (désolé pprzemek):

>>> import timeit
>>> S = 'Hey, you - what are you doing here!?'
>>> def my_split(s, seps):
...     res = [s]
...     for sep in seps:
...         s, res = res, []
...         for seq in s:
...             res += seq.split(sep)
...     return res
... 
>>> timeit.Timer('my_split(S, punctuation)', 'from __main__ import S,my_split; from string import punctuation').timeit()
54.65477919578552

Ensuite, nous utilisons re.findall() (comme indiqué par la réponse suggérée). Plus vite:

>>> timeit.Timer('findall(r"\w+", S)', 'from __main__ import S; from re import findall').timeit()
4.194725036621094

Enfin, nous utilisons translate:

>>> from string import translate,maketrans,punctuation 
>>> T = maketrans(punctuation, ' '*len(punctuation))
>>> timeit.Timer('translate(S, T).split()', 'from __main__ import S,T,translate').timeit()
1.2835021018981934

Explication:

string.translate est implémenté en C et contrairement à de nombreuses fonctions de manipulation de chaînes en Python, string.translate  ne fait pas produire une nouvelle chaîne. Donc, c'est à peu près aussi rapide que vous pouvez obtenir pour la substitution de chaînes.

C'est un peu gênant, cependant, car il a besoin d'une table de traduction pour faire cette magie. Vous pouvez créer une table de traduction avec le maketrans()fonction de commodité. L'objectif ici est de traduire tous les caractères indésirables en espaces. Un substitut un-pour-un. Encore une fois, aucune nouvelle donnée n'est produite. Alors ceci est vite!

Ensuite, nous utilisons le bon vieux split(). split() par défaut, fonctionnera sur tous les caractères d'espacement, les regroupant pour la division. Le résultat sera la liste des mots que vous voulez. Et cette approche est presque 4x plus rapide que re.findall()!


35
2017-08-30 04:05



Kinda late answer :), mais j'avais un dilemme similaire et je ne voulais pas utiliser le module 're'.

def my_split(s, seps):
    res = [s]
    for sep in seps:
        s, res = res, []
        for seq in s:
            res += seq.split(sep)
    return res

print my_split('1111  2222 3333;4444,5555;6666', [' ', ';', ','])
['1111', '', '2222', '3333', '4444', '5555', '6666']

20
2018-05-26 09:31



join = lambda x: sum(x,[])  # a.k.a. flatten1([[1],[2,3],[4]]) -> [1,2,3,4]
# ...alternatively...
join = lambda lists: [x for l in lists for x in l]

Ensuite, cela devient un trois lignes:

fragments = [text]
for token in tokens:
    fragments = join(f.split(token) for f in fragments)

Explication

C'est ce que Haskell appelle la monade de liste. L'idée derrière la monade est qu'une fois "dans la monade", vous "restez dans la monade" jusqu'à ce que quelque chose vous emmène. Par exemple dans Haskell, disons que vous mappez le python range(n) -> [1,2,...,n] fonctionner sur une liste. Si le résultat est une liste, il sera ajouté à la liste sur place, de sorte que vous obtiendrez quelque chose comme map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]. Ceci est connu comme map-append (ou mappend, ou peut-être quelque chose comme ça). L'idée ici est que vous avez cette opération que vous appliquez (scission sur un jeton), et chaque fois que vous faites cela, vous joignez le résultat dans la liste.

Vous pouvez l'abstraire dans une fonction et avoir tokens=string.punctuation par défaut.

Les avantages de cette approche:

  • Cette approche (contrairement aux approches basées sur des regex naïfs) peut fonctionner avec des jetons de longueur arbitraire (que regex peut aussi faire avec une syntaxe plus avancée).
  • Vous n'êtes pas limité à de simples jetons; vous pourriez avoir une logique arbitraire à la place de chaque jeton, par exemple l'un des «jetons» pourrait être une fonction qui se divise en fonction de la parenthèse imbriquée.

10
2018-05-05 08:35



Tout d'abord, je veux être d'accord avec les autres que le regex ou str.translate(...) les solutions basées sont les plus performantes. Pour mon cas d'utilisation, la performance de cette fonction n'était pas significative, donc je voulais ajouter des idées que j'ai considérées avec ces critères.

Mon objectif principal était de généraliser les idées de certaines des autres réponses en une solution qui pourrait fonctionner pour des chaînes contenant plus que des mots regex (ie, liste noire du sous-ensemble explicite de caractères de ponctuation vs caractères de mots en liste blanche).

Notez que, dans toute approche, on peut aussi envisager d'utiliser string.punctuation à la place d'une liste définie manuellement.

Option 1 - re.sub

J'ai été surpris de ne voir aucune réponse à ce jour re.sub (...). Je trouve que c'est une approche simple et naturelle à ce problème.

import re

my_str = "Hey, you - what are you doing here!?"

words = re.split(r'\s+', re.sub(r'[,\-!?]', ' ', my_str).strip())

Dans cette solution, j'ai imbriqué l'appel à re.sub(...) à l'intérieur re.split(...) - mais si la performance est critique, compiler la regex à l'extérieur pourrait être bénéfique - pour mon cas d'utilisation, la différence n'était pas significative, donc je préfère la simplicité et la lisibilité.

Option 2 - str.replace

Ceci est un peu plus de lignes, mais il a l'avantage d'être extensible sans avoir à vérifier si vous devez échapper à un certain caractère dans regex.

my_str = "Hey, you - what are you doing here!?"

replacements = (',', '-', '!', '?')
for r in replacements:
    my_str = my_str.replace(r, ' ')

words = my_str.split()

Il aurait été bien de pouvoir mapper le str.replace à la chaîne, mais je ne pense pas que cela puisse être fait avec des chaînes immuables, et pendant que le mappage avec une liste de caractères fonctionnerait, exécuter chaque remplacement sur chaque caractère semble excessif. (Edit: Voir l'option suivante pour un exemple fonctionnel.)

Option 3 - functools.reduce

(En Python 2, reduce est disponible dans l'espace de noms global sans l'importer de functools.)

import functools

my_str = "Hey, you - what are you doing here!?"

replacements = (',', '-', '!', '?')
my_str = functools.reduce(lambda s, sep: s.replace(sep, ' '), replacements, my_str)
words = my_str.split()

7
2017-11-10 17:31



essaye ça:

import re

phrase = "Hey, you - what are you doing here!?"
matches = re.findall('\w+', phrase)
print matches

cela va imprimer ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']


4
2018-06-29 18:01