841 Stimmen

Strings in Wörter mit mehreren Wortbegrenzern aufteilen

Ich denke, was ich tun möchte, ist eine ziemlich häufige Aufgabe, aber ich habe keine Referenz im Web gefunden. Ich habe Text mit Satzzeichen und möchte eine Liste der Wörter.

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

sollte sein

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

Aber Pythons str.split() funktioniert nur mit einem Argument, also habe ich alle Wörter mit dem Satzzeichen, nachdem ich mit Leerzeichen aufgeteilt habe. Irgendwelche Ideen?

7 Stimmen

13 Stimmen

Python's str.split() funktioniert auch ohne Argumente überhaupt

16voto

Taylor D. Edmiston Punkte 10409

Zunächst möchte ich zustimmen, dass die Regex- oder str.translate(...)-basierten Lösungen am performantesten sind. In meinem Anwendungsfall war die Leistung dieser Funktion nicht signifikant, daher wollte ich Ideen hinzufügen, die ich unter diesem Kriterium in Betracht gezogen habe.

Mein Hauptziel war es, Ideen aus einigen der anderen Antworten zu verallgemeinern und in eine Lösung zu integrieren, die für Zeichenfolgen funktioniert, die mehr als nur Regex-Wörter enthalten (d.h. das Blacklisten des expliziten Untermengen von Satzzeichenzeichen gegen Whitelist-Wörterzeichen).

Beachten Sie, dass man in jedem Ansatz auch in Erwägung ziehen könnte, string.punctuation anstelle einer manuell definierten Liste zu verwenden.

Option 1 - re.sub

Es hat mich überrascht, dass bisher noch keine Antwort re.sub(...) verwendet. Ich finde es einen einfachen und natürlichen Ansatz für dieses Problem.

import re

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

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

In dieser Lösung habe ich den Aufruf von re.sub(...) innerhalb von re.split(...) verschachtelt - aber wenn die Leistung entscheidend ist, könnte es von Vorteil sein, das Regex außerhalb zu kompilieren. Für meinen Anwendungsfall war der Unterschied nicht signifikant, daher bevorzuge ich Einfachheit und Lesbarkeit.

Option 2 - str.replace

Es sind ein paar Zeilen mehr, aber es hat den Vorteil, ohne Überprüfung, ob ein bestimmtes Zeichen in Regex maskiert werden muss, erweiterbar zu sein.

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

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

words = my_str.split()

Es wäre schön gewesen, das str.replace direkt auf die Zeichenfolge zu mappen, aber ich glaube nicht, dass dies mit unveränderlichen Strings möglich ist. Und während das Mappen gegen eine Liste von Zeichen funktionieren würde, klingt es übertrieben, jede Ersetzung gegen jedes Zeichen auszuführen. (Edit: Siehe nächste Option für ein funktionales Beispiel.)

Option 3 - functools.reduce

(In Python 2 ist reduce im globalen Namespace verfügbar, ohne es aus functools zu importieren.)

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

0 Stimmen

Hmm, eine weitere Methode ist die Verwendung von str.translate - sie ist nicht für Unicode geeignet, aber wahrscheinlich schneller als andere Methoden und kann daher in einigen Fällen nützlich sein: replacements=',-!?'; import string; my_str = my_str.translate(string.maketrans(replacements, ' ' * len(replacements))) Hier ist es auch zwingend erforderlich, dass die Ersetzungen als eine Zeichenfolge von Zeichen vorliegen und nicht als Tupel oder Liste.

0 Stimmen

@MarSoft Danke! Ich habe das am Anfang der Antwort erwähnt, aber beschlossen, es nicht hinzuzufügen, da die vorhandenen Antworten es bereits gut besprochen haben.

10voto

ninjagecko Punkte 82995
beitreten = lambda x: sum(x,[])  # a.k.a. flach1([[1],[2,3],[4]]) -> [1,2,3,4]
# ...alternativ...
beitreten = lambda listen: [x for l in listen for x in l]

Dann wird dies zu einem Drei-Zeiler:

fragmente = [text]
für token in tokens:
    fragmente = beitreten(f.split(token) für f in fragmente)

Erklärung

Dies ist das, was in Haskell als der List Monad bekannt ist. Die Idee hinter dem Monad ist, dass einmal "im Monad" du "im Monad bleibst", bis dich etwas herausnimmt. Zum Beispiel in Haskell, wenn du die Python range(n) -> [1,2,...,n] Funktion über eine Liste mapst. Wenn das Ergebnis eine Liste ist, wird es direkt an die Liste angehängt, also würdest du etwas wie map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0] bekommen. Dies ist als map-append bekannt. Die Idee hier ist, dass du diese Operation, die du anwendest (Splitting an einem Token), hast und immer wenn du das tust, fügst du das Ergebnis in die Liste ein.

Du kannst dies in eine Funktion abstrahieren und standardmäßig tokens=string.punctuation setzen.

Vorteile dieses Ansatzes:

  • Dieser Ansatz (im Gegensatz zu naiven regex-basierten Ansätzen) kann mit Tokens beliebiger Länge arbeiten (was Regex auch mit einer fortgeschritteneren Syntax tun kann).
  • Du bist nicht auf einfache Tokens beschränkt; du könntest beliebige Logik anstelle jedes Tokens haben, zum Beispiel könnte eines der "Tokens" eine Funktion sein, die entsprechend der verschachtelten Klammern aufteilt.

0 Stimmen

Sauberer Haskell-Lösungsansatz, aber meiner Meinung nach kann dies in Python klarer ohne Mappend geschrieben werden.

0 Stimmen

@Gans: Der Punkt war, dass die 2-Zeilen-Funktion map_then_append verwendet werden kann, um ein Problem zu einem 2-Zeiler zu machen, sowie viele andere Probleme viel einfacher zu schreiben. Die meisten anderen Lösungen verwenden das reguläre Ausdrucksmodul re, was nicht Python ist. Aber ich war unzufrieden damit, wie meine Antwort unelegant und aufgebläht erscheint, wenn sie eigentlich prägnant ist ... Ich werde es bearbeiten...

0 Stimmen

Ist es so gedacht, dass dies in Python wie geschrieben funktioniert? Mein fragments-Ergebnis ist nur eine Liste der Zeichen im String (einschließlich der Token).

9voto

monitorius Punkte 3096

Ich mag re, aber hier ist meine Lösung ohne es:

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__ ist eine Methode, die vom 'in' Operator verwendet wird. Im Grunde ist es das gleiche wie

lambda ch: ch in sep

aber ist hier praktischer.

groupby nimmt unseren String und eine Funktion. Es teilt den String in Gruppen unter Verwendung dieser Funktion auf: immer wenn sich der Wert der Funktion ändert, wird eine neue Gruppe generiert. Also ist sep.__contains__ genau das, was wir brauchen.

groupby gibt eine Sequenz von Paaren zurück, wobei pair[0] das Ergebnis unserer Funktion und pair[1] die Gruppe ist. Mit 'if not k' filtern wir die Gruppen mit Trennzeichen heraus (weil das Ergebnis von sep.__contains__ auf Trennzeichen True ist). Nun haben wir eine Sequenz von Gruppen, wobei jede ein Wort ist (eine Gruppe ist eigentlich ein Iterable, daher verwenden wir join, um sie in einen String umzuwandeln).

Diese Lösung ist ziemlich allgemein, weil sie eine Funktion zum Aufteilen des Strings verwendet (man kann nach beliebiger Bedingung aufteilen). Außerdem erstellt sie keine Zwischenstrings/-listen (man kann join entfernen und der Ausdruck wird träge, da jede Gruppe ein Iterator ist)

6voto

jeroen Punkte 61

Verwenden Sie zweimal replace:

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

ergebnisse in:

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

5voto

Corey Goldberg Punkte 56036

Versuche dies:

import re

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

dies wird ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here'] ausgeben

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X