Wie teile ich in Python eine Zeichenfolge und behalte die Trennzeichen bei?

225

Hier ist der einfachste Weg, dies zu erklären. Folgendes verwende ich:

re.split('\W', 'foo/bar spam\neggs')
-> ['foo', 'bar', 'spam', 'eggs']

Folgendes möchte ich:

someMethod('\W', 'foo/bar spam\neggs')
-> ['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']

Der Grund ist, dass ich eine Zeichenfolge in Token aufteilen, manipulieren und dann wieder zusammensetzen möchte.

Ken Kinder
quelle
2
Wofür steht das \W? Ich habe es bei Google nicht geschafft.
Ooker
6
Ein Nicht-Wort- Charakter siehe hier für Details
Russell

Antworten:

294
>>> re.split('(\W)', 'foo/bar spam\neggs')
['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']
Commodore Jaeger
quelle
21
Das ist cool. Ich wusste nicht, dass re.split das mit Capture-Gruppen macht.
Laurence Gonsalves
15
@Laurence: Nun, es ist dokumentiert: docs.python.org/library/re.html#re.split : "Zeichenfolge durch das Auftreten von Mustern teilen. Wenn im Muster Klammern erfasst werden, wird der Text aller Gruppen im Muster verwendet werden auch als Teil der resultierenden Liste zurückgegeben. "
Vinay Sajip
39
Es ist ernsthaft unterdokumentiert. Ich benutze Python seit 14 Jahren und habe es gerade erst herausgefunden.
smci
18
Gibt es eine Option, mit der die Ausgabe der Gruppenübereinstimmung an die Links (oder analog rechts) der Aufteilung angehängt wird? Kann dies beispielsweise leicht geändert werden, sodass die Ausgabe erfolgt ['foo', '/bar', ' spam', '\neggs']?
ely
2
@ Mr.F Möglicherweise können Sie mit re etwas tun. Ich wollte mich auf ein Endprozent teilen, also habe ich mich nur in einen Doppelcharakter eingeteilt und mich dann geteilt, hacky, aber für meinen Fall gearbeitet: re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot'))->['5.000%', 'Additional Whatnot']
Kyle James Walker
28

Wenn Sie auf Newline teilen, verwenden Sie splitlines(True).

>>> 'line 1\nline 2\nline without newline'.splitlines(True)
['line 1\n', 'line 2\n', 'line without newline']

(Keine allgemeine Lösung, aber hier hinzufügen, falls jemand hierher kommt und nicht merkt, dass diese Methode existiert.)

Mark Lodato
quelle
12

Eine weitere No-Regex-Lösung, die unter Python 3 gut funktioniert

# Split strings and keep separator
test_strings = ['<Hello>', 'Hi', '<Hi> <Planet>', '<', '']

def split_and_keep(s, sep):
   if not s: return [''] # consistent with string.split()

   # Find replacement character that is not used in string
   # i.e. just use the highest available character plus one
   # Note: This fails if ord(max(s)) = 0x10FFFF (ValueError)
   p=chr(ord(max(s))+1) 

   return s.replace(sep, sep+p).split(p)

for s in test_strings:
   print(split_and_keep(s, '<'))


# If the unicode limit is reached it will fail explicitly
unicode_max_char = chr(1114111)
ridiculous_string = '<Hello>'+unicode_max_char+'<World>'
print(split_and_keep(ridiculous_string, '<'))
ootwch
quelle
10

Wenn Sie nur 1 Trennzeichen haben, können Sie Listenverständnisse anwenden:

text = 'foo,bar,baz,qux'  
sep = ','

Trennzeichen anhängen / voranstellen:

result = [x+sep for x in text.split(sep)]
#['foo,', 'bar,', 'baz,', 'qux,']
# to get rid of trailing
result[-1] = result[-1].strip(sep)
#['foo,', 'bar,', 'baz,', 'qux']

result = [sep+x for x in text.split(sep)]
#[',foo', ',bar', ',baz', ',qux']
# to get rid of trailing
result[0] = result[0].strip(sep)
#['foo', ',bar', ',baz', ',qux']

Trennzeichen als eigenes Element:

result = [u for x in text.split(sep) for u in (x, sep)]
#['foo', ',', 'bar', ',', 'baz', ',', 'qux', ',']
results = result[:-1]   # to get rid of trailing
Granitosaurus
quelle
1
Sie können auch hinzufügen, if xum sicherzustellen, dass das von produzierte Stück spliteinen gewissen Inhalt hat, dhresult = [x + sep for x in text.split(sep) if x]
ich habe Alien
Für mich wurde der Streifen zu stark entfernt und ich musste diesen verwenden:result = [sep+x for x in data.split(sep)] result[0] = result[0][len(sep):]
scottlittle
9

Ein weiteres Beispiel: Teilen Sie nicht alphanumerisch und behalten Sie die Trennzeichen bei

import re
a = "foo,bar@candy*ice%cream"
re.split('([^a-zA-Z0-9])',a)

Ausgabe:

['foo', ',', 'bar', '@', 'candy', '*', 'ice', '%', 'cream']

Erläuterung

re.split('([^a-zA-Z0-9])',a)

() <- keep the separators
[] <- match everything in between
^a-zA-Z0-9 <-except alphabets, upper/lower and numbers.
Anurag
quelle
Obwohl dies, wie in den Dokumenten angegeben , der akzeptierten Antwort entspricht, gefällt mir die Lesbarkeit dieser Version - auch wenn dies \Weine kompaktere Möglichkeit ist, sie auszudrücken.
Ephsmith
3

Sie können eine Zeichenfolge auch mit einem Array von Zeichenfolgen anstelle eines regulären Ausdrucks wie folgt teilen:

def tokenizeString(aString, separators):
    #separators is an array of strings that are being used to split the the string.
    #sort separators in order of descending length
    separators.sort(key=len)
    listToReturn = []
    i = 0
    while i < len(aString):
        theSeparator = ""
        for current in separators:
            if current == aString[i:i+len(current)]:
                theSeparator = current
        if theSeparator != "":
            listToReturn += [theSeparator]
            i = i + len(theSeparator)
        else:
            if listToReturn == []:
                listToReturn = [""]
            if(listToReturn[-1] in separators):
                listToReturn += [""]
            listToReturn[-1] += aString[i]
            i += 1
    return listToReturn


print(tokenizeString(aString = "\"\"\"hi\"\"\" hello + world += (1*2+3/5) '''hi'''", separators = ["'''", '+=', '+', "/", "*", "\\'", '\\"', "-=", "-", " ", '"""', "(", ")"]))
Anderson Green
quelle
3
# This keeps all separators  in result 
##########################################################################
import re
st="%%(c+dd+e+f-1523)%%7"
sh=re.compile('[\+\-//\*\<\>\%\(\)]')

def splitStringFull(sh, st):
   ls=sh.split(st)
   lo=[]
   start=0
   for l in ls:
     if not l : continue
     k=st.find(l)
     llen=len(l)
     if k> start:
       tmp= st[start:k]
       lo.append(tmp)
       lo.append(l)
       start = k + llen
     else:
       lo.append(l)
       start =llen
   return lo
  #############################

li= splitStringFull(sh , st)
['%%(', 'c', '+', 'dd', '+', 'e', '+', 'f', '-', '1523', ')%%', '7']
Moisey Oysgelt
quelle
2

Eine faule und einfache Lösung

Angenommen, Ihr Regex-Muster ist split_pattern = r'(!|\?)'

Zuerst fügen Sie dasselbe Zeichen wie das neue Trennzeichen hinzu, z. B. '[Ausschneiden]'.

new_string = re.sub(split_pattern, '\\1[cut]', your_string)

Dann teilen Sie das neue Trennzeichen, new_string.split('[cut]')

Yilei Wang
quelle
Dieser Ansatz ist clever, schlägt jedoch fehl, wenn die ursprüngliche Zeichenfolge bereits [cut]irgendwo enthalten ist.
Matthijs Kooijman
1

Wenn Sie eine Zeichenfolge teilen möchten, während die Trennzeichen durch Regex beibehalten werden, ohne eine Gruppe zu erfassen:

def finditer_with_separators(regex, s):
    matches = []
    prev_end = 0
    for match in regex.finditer(s):
        match_start = match.start()
        if (prev_end != 0 or match_start > 0) and match_start != prev_end:
            matches.append(s[prev_end:match.start()])
        matches.append(match.group())
        prev_end = match.end()
    if prev_end < len(s):
        matches.append(s[prev_end:])
    return matches

regex = re.compile(r"[\(\)]")
matches = finditer_with_separators(regex, s)

Wenn man annimmt, dass Regex in eine Erfassungsgruppe eingepackt ist:

def split_with_separators(regex, s):
    matches = list(filter(None, regex.split(s)))
    return matches

regex = re.compile(r"([\(\)])")
matches = split_with_separators(regex, s)

In beiden Fällen werden auch leere Gruppen entfernt, die in den meisten Fällen nutzlos und ärgerlich sind.

Dmitriy Sintsov
quelle
0

Ich hatte ein ähnliches Problem beim Versuch, einen Dateipfad aufzuteilen, und hatte Mühe, eine einfache Antwort zu finden. Dies funktionierte für mich und beinhaltete nicht, dass Trennzeichen wieder in den geteilten Text eingesetzt werden mussten:

my_path = 'folder1/folder2/folder3/file1'

import re

re.findall('[^/]+/|[^/]+', my_path)

kehrt zurück:

['folder1/', 'folder2/', 'folder3/', 'file1']

Conor
quelle
Dies kann leicht vereinfacht werden, indem Folgendes verwendet wird: re.findall('[^/]+/?', my_path)(z. B. das Optionieren des abschließenden Schrägstrichs mithilfe von a, ?anstatt zwei Alternativen |
bereitzustellen
0

Ich fand diesen generatorbasierten Ansatz befriedigender:

def split_keep(string, sep):
    """Usage:
    >>> list(split_keep("a.b.c.d", "."))
    ['a.', 'b.', 'c.', 'd']
    """
    start = 0
    while True:
        end = string.find(sep, start) + 1
        if end == 0:
            break
        yield string[start:end]
        start = end
    yield string[start:]

Es vermeidet die Notwendigkeit, den richtigen regulären Ausdruck herauszufinden, während es theoretisch ziemlich billig sein sollte. Es werden keine neuen Zeichenfolgenobjekte erstellt und der größte Teil der Iterationsarbeit wird an die effiziente Suchmethode delegiert.

... und in Python 3.8 kann es so kurz sein wie:

def split_keep(string, sep):
    start = 0
    while (end := string.find(sep, start) + 1) > 0:
        yield string[start:end]
        start = end
    yield string[start:]
Chen Levy
quelle
0
  1. Ersetzen Sie alle seperator: (\W)durchseperator + new_seperator: (\W;)

  2. geteilt durch die new_seperator: (;)

def split_and_keep(seperator, s):
  return re.split(';', re.sub(seperator, lambda match: match.group() + ';', s))

print('\W', 'foo/bar spam\neggs')
Kobako
quelle
0

Hier ist eine einfache .splitLösung, die ohne Regex funktioniert.

Dies ist eine Antwort für Python split (), ohne das Trennzeichen zu entfernen , also nicht genau das, was der ursprüngliche Beitrag verlangt, aber die andere Frage wurde als Duplikat für diesen geschlossen.

def splitkeep(s, delimiter):
    split = s.split(delimiter)
    return [substr + delimiter for substr in split[:-1]] + [split[-1]]

Zufallstests:

import random

CHARS = [".", "a", "b", "c"]
assert splitkeep("", "X") == [""]  # 0 length test
for delimiter in ('.', '..'):
    for idx in range(100000):
        length = random.randint(1, 50)
        s = "".join(random.choice(CHARS) for _ in range(length))
        assert "".join(splitkeep(s, delimiter)) == s
orestisf
quelle