web-dev-qa-db-fra.com

Fractionner des chaînes en mots avec plusieurs délimiteurs de limites de mots

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

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

devrait être

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

Mais la fonction str.split() de Python ne fonctionne qu'avec un seul argument, aussi tous les mots sont-ils avec la ponctuation après la séparation avec des espaces. Des idées?

565
ooboo

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']
396
RichieHindle

re.split ()

re.split (modèle, chaîne [ maxsplit = 0])

Fractionner la chaîne par les occurrences du motif. Si des parenthèses de capture sont utilisées dans le modèle, le texte de tous les groupes du modèle est également renvoyé dans la liste résultante. Si maxsplit est différent de zéro, la plupart des divisions maxsplit se produisent et le reste de la chaîne est renvoyé en tant qu'é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é résolu 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.']
471
gimel

Un autre moyen rapide de le faire sans expression régulière consiste à remplacer les caractères en premier, comme ci-dessous:

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

Tant de réponses, mais je ne trouve aucune solution qui réponde efficacement à la demande littérale du title (fractionnement en plusieurs séparateurs possibles). De nombreuses réponses suppriment plutôt tout ce qui n'est pas un mot, ce qui est différent ). Voici donc une réponse à la question dans le titre, qui s'appuie sur le module standard et efficace re de Python:

>>> 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ù:

  • le […] correspond à un des séparateurs listés à l'intérieur,
  • le \- dans l'expression régulière est là pour empêcher l'interprétation spéciale de - en tant qu'indicateur de plage de caractères (comme dans A-Z),
  • le + ignore un ou plusieurs délimiteurs (il pourrait être omis grâce au filter(), mais cela produirait inutilement des chaînes vides entre séparateurs correspondants)
  • filter(None, …) supprime les chaînes vides éventuellement créées par les séparateurs de début et de fin (étant donné que les chaînes vides ont une valeur fausse booléenne).

Ce re.split() justement "se scinde avec plusieurs séparateurs", comme demandé dans le titre de la question.

Cette solution est en outre à l'abri des problèmes liés aux caractères non-ASCII dans les mots trouvés dans d'autres solutions (voir le premier commentaire de réponse de ghostdog74 ).

Le module re est beaucoup plus efficace (en vitesse et en concision) que de faire des boucles et des tests Python "à la main"!

244
Eric O Lebigot

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()
53
ghostdog74

Astuce: utilisez string.translate pour les opérations de chaîne les plus rapides que Python effectue.

Quelques preuves ...

Tout d’abord, le chemin lent (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é dans 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 produit pas une nouvelle chaîne. Donc, c'est à peu près aussi rapide que vous pouvez obtenir pour la substitution de chaîne.

C'est un peu gênant, cependant, car il faut une table de traduction pour faire cette magie. Vous pouvez créer une table de traduction avec la fonction pratique maketrans(). 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. Donc c'est vite !

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

36
Dave

Un peu en retard :), 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']
22
pprzemek
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]

Cela devient alors une ligne à trois:

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

Explication

C'est ce que Haskell appelle la monade Liste. L'idée derrière la monade est qu'une fois "dans la monade", vous "restiez dans la monade" jusqu'à ce que quelque chose vous éloigne. Par exemple, dans Haskell, imaginons que vous mappiez la fonction python range(n) -> [1,2,...,n] sur une liste. Si le résultat est une liste, elle sera ajoutée à 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]. Cela s'appelle map-append (ou mappend, ou peut-être quelque chose comme ça). L'idée ici est que vous appliquez cette opération (fractionnement sur un jeton) et que, chaque fois que vous le faites, vous insérez le résultat dans la liste.

Vous pouvez résumer cela en une fonction et avoir tokens=string.punctuation par défaut. 

Avantages de cette approche:

  • Cette approche (contrairement aux approches naïves à base de regex) peut fonctionner avec des jetons de longueur arbitraire (ce que regex peut également utiliser avec une syntaxe plus avancée).
  • Vous n'êtes pas limité à de simples jetons; vous pouvez avoir une logique arbitraire à la place de chaque jeton, par exemple l'un des "jetons" peut être une fonction qui se divise en fonction de la manière dont les parenthèses sont imbriquées.
10
ninjagecko

Premièrement, je suis d’accord avec les autres pour dire que les solutions basées sur regex ou str.translate(...) sont les plus performantes. Pour mon cas d'utilisation, les performances de cette fonction n'étaient pas significatives, je voulais donc ajouter des idées que je considérais avec ce critère.

Mon objectif principal était de généraliser les idées de certaines des autres réponses en une solution qui puisse fonctionner pour des chaînes contenant davantage que des mots d'expression rationnelle (c'est-à-dire, mettre en liste noire le sous-ensemble explicite de caractères de ponctuation par rapport aux caractères de mot en liste blanche).

Notez que, dans n'importe quelle approche, on peut également 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 jusqu'à présent utilise re.sub (...) . Je trouve cela 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 de re.sub(...) à l'intérieur de re.split(...) - mais si les performances sont essentielles, la compilation de la regex à l'extérieur peut s'avérer bénéfique - dans mon cas d'utilisation, la différence n'était pas significative, je préfère donc la simplicité et la lisibilité.

Option 2 - str.replace

Ce sont quelques lignes de plus, mais elles ont l’avantage d’être extensibles sans avoir à vérifier si vous devez échapper à un caractère donné 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é agréable de pouvoir mapper le str.replace sur la chaîne, mais je ne pense pas que cela puisse être fait avec des chaînes immuables, et tout en mappant sur une liste de caractères fonctionnerait, lancer chaque remplacement contre 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 un espace de noms global sans l'importer à partir 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()
9
Taylor Edmiston

essaye ça:

import re

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

ceci imprimera ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

4
Corey Goldberg

Utilisez remplacer deux fois:

a = '11223FROM33344INTO33222FROM3344'
a.replace('FROM', ',,,').replace('INTO', ',,,').split(',,,')

résulte en: 

['11223', '33344', '33222', '3344']
4
jeroen

J'aime re , mais voici ma solution sans elle:

from itertools import groupby
sep = ' ,-!?'
s = "Hey, you - what are you doing here!?"
print [''.join(g) for k, g in groupby(s, sep.__contains__) if not k]

sep .__ contains__ est une méthode utilisée par l'opérateur 'in'. Fondamentalement, c'est la même chose que

lambda ch: ch in sep

mais est plus pratique ici.

groupby obtient notre chaîne et notre fonction. Il divise la chaîne en groupes utilisant cette fonction: chaque fois qu'une valeur de fonction est modifiée, un nouveau groupe est généré. Donc, sep .__ contient__ est exactement ce dont nous avons besoin.

groupby renvoie une séquence de paires, où paire [0] est le résultat de notre fonction et paire [1] est un groupe. En utilisant 'si pas k' nous filtrons les groupes avec des séparateurs (car le résultat de sep .__ contient__ est vrai sur les séparateurs). Eh bien, c'est tout - nous avons maintenant une séquence de groupes où chacun est un mot (le groupe est en fait un itératif, nous utilisons donc join pour le convertir en chaîne).

Cette solution est assez générale car elle utilise une fonction pour séparer les chaînes (vous pouvez diviser en fonction de la condition requise). En outre, il ne crée pas de chaînes/listes intermédiaires (vous pouvez supprimer join et l'expression deviendra paresseuse, chaque groupe étant un itérateur).

4
monitorius

Au lieu d'utiliser une fonction re module, vous pouvez obtenir le même résultat en utilisant la méthode series.str.split de pandas. 

Commencez par créer une série avec la chaîne ci-dessus, puis appliquez la méthode à la série.

thestring = pd.Series("Hey, you - what are you doing here!?") thestring.str.split(pat = ',|-')

paramètre pat prend les délimiteurs et retourne la chaîne scindée sous forme de tableau. Ici, les deux délimiteurs sont passés en utilisant un | (ou opérateur) . La sortie est la suivante:

[Hey, you , what are you doing here!?]  

3
Tarun Kumar Yellapu

Je me familiarisais de nouveau avec Python et j'avais besoin de la même chose ... La solution de recherche globale était peut-être meilleure, mais j'ai proposé ceci:

tokens = [x.strip() for x in data.split(',')]
3
Leon Starr

Créez une fonction qui prend en entrée deux chaînes (la chaîne source à scinder et la chaîne de délimiteurs de la liste scindée) et génère une liste de mots scindés:

def split_string(source, splitlist):
    output = []  # output list of cleaned words
    atsplit = True
    for char in source:
        if char in splitlist:
            atsplit = True
        else:
            if atsplit:
                output.append(char)  # append new Word after split
                atsplit = False
            else: 
                output[-1] = output[-1] + char  # continue copying characters until next split
    return output
1
user852006

Une autre méthode consiste à utiliser la trousse à outils en langage naturel ( nltk ).

import nltk
data= "Hey, you - what are you doing here!?"
Word_tokens = nltk.tokenize.regexp_tokenize(data, r'\w+')
print Word_tokens

Ceci imprime: ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

Le plus gros inconvénient de cette méthode est qu'il vous faut installer le paquet nltk .

Les avantages sont que vous pouvez faire beaucoup de choses amusantes avec le reste du paquet nltk une fois vos jetons obtenus.

1
tgray

Tout d’abord, utilisez toujours re.compile () avant d’effectuer une opération RegEx dans une boucle, car elle fonctionne plus rapidement que les opérations normales.

pour votre problème, commencez par compiler le motif, puis effectuez une action dessus.

import re
DATA = "Hey, you - what are you doing here!?"
reg_tok = re.compile("[\w']+")
print reg_tok.findall(DATA)
1
shrikant

Tout d'abord, je ne pense pas que votre intention soit d'utiliser réellement la ponctuation comme délimiteurs dans les fonctions de division. Votre description suggère que vous souhaitiez simplement éliminer la ponctuation des chaînes résultantes.

Je rencontre cela assez souvent, et ma solution habituelle n’exige pas de ré.

Fonction lambda à une ligne avec compréhension de la liste:

(nécessite import string):

split_without_punc = lambda text : [Word.strip(string.punctuation) for Word in 
    text.split() if Word.strip(string.punctuation) != '']

# Call function
split_without_punc("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']


Fonction (traditionnelle)

En tant que fonction traditionnelle, il ne reste que deux lignes avec une liste de compréhension (en plus de import string):

def split_without_punctuation2(text):

    # Split by whitespace
    words = text.split()

    # Strip punctuation from each Word
    return [Word.strip(ignore) for Word in words if Word.strip(ignore) != '']

split_without_punctuation2("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

Cela laissera aussi naturellement les contractions et les mots avec un trait d'union intacts. Vous pouvez toujours utiliser text.replace("-", " ") pour transformer les traits d'union en espaces avant la division.

Fonction générale sans compréhension lambda ou liste

Pour une solution plus générale (où vous pouvez spécifier les caractères à éliminer), et sans compréhension de la liste, vous obtenez:

def split_without(text: str, ignore: str) -> list:

    # Split by whitespace
    split_string = text.split()

    # Strip any characters in the ignore string, and ignore empty strings
    words = []
    for Word in split_string:
        Word = Word.strip(ignore)
        if Word != '':
            words.append(Word)

    return words

# Situation-specific call to general function
import string
final_text = split_without("Hey, you - what are you doing?!", string.punctuation)
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

Bien sûr, vous pouvez toujours généraliser la fonction lambda à toute chaîne de caractères spécifiée.

1
cosmicFluke

Voici la réponse avec quelques explications.

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

# replace all the non alpha-numeric with space and then join.
new_string = ''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])
# output of new_string
'Hey  you  what are you doing here  '

# str.split() will remove all the empty string if separator is not provided
new_list = new_string.split()

# output of new_list
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

# we can join it to get a complete string without any non alpha-numeric character
' '.join(new_list)
# output
'Hey you what are you doing'

ou en une ligne, on peut faire comme ça:

(''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])).split()

# output
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

réponse mise à jour

1
Tasneem Haider

en utilisant maketrans et traduire vous pouvez le faire facilement et proprement

import string
specials = ',.!?:;"()<>[]#$=-/'
trans = string.maketrans(specials, ' '*len(specials))
body = body.translate(trans)
words = body.strip().split()
1
Ritesh Sinha

Je pense que ce qui suit est la meilleure réponse à vos besoins:

\W+ peut convenir à ce cas, mais peut ne pas convenir à d'autres cas.

filter(None, re.compile('[ |,|\-|!|?]').split( "Hey, you - what are you doing here!?")
0
nemozhp

Voici mon départ pour une scission avec plusieurs déliminateurs:

def msplit( str, delims ):
  w = ''
  for z in str:
    if z not in delims:
        w += z
    else:
        if len(w) > 0 :
            yield w
        w = ''
  if len(w) > 0 :
    yield w
0
Martlark

j'ai eu le même problème que @ooboo et j'ai trouvé ce sujet @ ghostdog74 m'a inspiré, peut-être que quelqu'un trouve ma solution utile

str1='adj:sg:nom:m1.m2.m3:pos'
splitat=':.'
''.join([ s if s not in splitat else ' ' for s in str1]).split()

saisissez quelque chose dans l'espace et divisez-le en utilisant le même caractère si vous ne voulez pas diviser en espaces.

0
badas

Je devais trouver ma propre solution car tout ce que j'avais testé jusqu'à présent avait échoué à un moment donné.

>>> import re
>>> def split_words(text):
...     rgx = re.compile(r"((?:(?<!'|\w)(?:\w-?'?)+(?<!-))|(?:(?<='|\w)(?:\w-?'?)+(?=')))")
...     return rgx.findall(text)

Cela semble bien fonctionner, du moins pour les exemples ci-dessous.

>>> split_words("The hill-tops gleam in morning's spring.")
['The', 'hill-tops', 'gleam', 'in', "morning's", 'spring']
>>> split_words("I'd say it's James' 'time'.")
["I'd", 'say', "it's", "James'", 'time']
>>> split_words("tic-tac-toe's tic-tac-toe'll tic-tac'tic-tac we'll--if tic-tac")
["tic-tac-toe's", "tic-tac-toe'll", "tic-tac'tic-tac", "we'll", 'if', 'tic-tac']
>>> split_words("google.com [email protected] split_words")
['google', 'com', 'email', 'google', 'com', 'split_words']
>>> split_words("Kurt Friedrich Gödel (/ˈɡɜːrdəl/;[2] German: [ˈkʊɐ̯t ˈɡøːdl̩] (listen);")
['Kurt', 'Friedrich', 'Gödel', 'ˈɡɜːrdəl', '2', 'German', 'ˈkʊɐ', 't', 'ˈɡøːdl', 'listen']
>>> split_words("April 28, 1906 – January 14, 1978) was an Austro-Hungarian-born Austrian...")
['April', '28', '1906', 'January', '14', '1978', 'was', 'an', 'Austro-Hungarian-born', 'Austrian']
0
Wood
def get_words(s):
    l = []
    w = ''
    for c in s.lower():
        if c in '-!?,. ':
            if w != '': 
                l.append(w)
            w = ''
        else:
            w = w + c
    if w != '': 
        l.append(w)
    return l

Voici l'usage:

>>> s = "Hey, you - what are you doing here!?"
>>> print get_words(s)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
0
inspectorrr

J'aime la manière replace() le meilleur. La procédure suivante remplace tous les séparateurs définis dans la chaîne splitlist par le premier séparateur de splitlist, puis divise le texte sur ce séparateur. Il faut également savoir si splitlist se trouve être une chaîne vide. Il retourne une liste de mots, sans chaîne vide.

def split_string(text, splitlist):
    for sep in splitlist:
        text = text.replace(sep, splitlist[0])
    return filter(None, text.split(splitlist[0])) if splitlist else [text]
0

Heres mon point de vue sur elle ....

def split_string(source,splitlist):
    splits = frozenset(splitlist)
    l = []
    s1 = ""
    for c in source:
        if c in splits:
            if s1:
                l.append(s1)
                s1 = ""
        else:
            print s1
            s1 = s1 + c
    if s1:
        l.append(s1)
    return l

>>>out = split_string("First Name,Last Name,Street Address,City,State,Zip Code",",")
>>>print out
>>>['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code']
0

J'ai récemment eu besoin de faire cela, mais je voulais une fonction qui corresponde quelque peu à la fonction str.split de la bibliothèque standard. Cette fonction se comporte de la même façon que la bibliothèque standard lorsqu'elle est appelée avec 0 ou 1 argument.

def split_many(string, *separators):
    if len(separators) == 0:
        return string.split()
    if len(separators) > 1:
        table = {
            ord(separator): ord(separator[0])
            for separator in separators
        }
        string = string.translate(table)
    return string.split(separators[0])

NOTE: Cette fonction n'est utile que lorsque vos séparateurs sont constitués d'un seul caractère (comme ce fut mon cas d'utilisation).

0
justinfay

J'aime la solution de pprzemek car elle ne suppose pas que les délimiteurs sont des caractères uniques et n'essaye pas de tirer parti d'une expression régulière (ce qui ne fonctionnerait pas bien si le nombre de séparateurs devait être long).

Voici une version plus lisible de la solution ci-dessus pour plus de clarté:

def split_string_on_multiple_separators(input_string, separators):
    buffer = [input_string]
    for sep in separators:
        strings = buffer
        buffer = []  # reset the buffer
        for s in strings:
            buffer = buffer + s.split(sep)

    return buffer
0
Everett

Si vous souhaitez une opération réversible (conservez les délimiteurs), vous pouvez utiliser cette fonction:

def tokenizeSentence_Reversible(sentence):
    setOfDelimiters = ['.', ' ', ',', '*', ';', '!']
    listOfTokens = [sentence]

    for delimiter in setOfDelimiters:
        newListOfTokens = []
        for ind, token in enumerate(listOfTokens):
            ll = [([delimiter, w] if ind > 0 else [w]) for ind, w in enumerate(token.split(delimiter))]
            listOfTokens = [item for sublist in ll for item in sublist] # flattens.
            listOfTokens = filter(None, listOfTokens) # Removes empty tokens: ''
            newListOfTokens.extend(listOfTokens)

        listOfTokens = newListOfTokens

    return listOfTokens
0
Nadav B