Dengan Python, bagaimana cara memisahkan string dan menjaga pemisah?

226

Inilah cara paling sederhana untuk menjelaskan ini. Inilah yang saya gunakan:

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

Inilah yang saya inginkan:

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

Alasannya adalah saya ingin membagi string menjadi token, memanipulasinya, lalu memasangnya kembali.

Ken Kinder
sumber
3
apa artinya \W? Saya gagal di google itu.
Ooker
8
Sebuah non-kata karakter lihat di sini untuk detail
Russell
Untuk pertanyaan yang diterapkan pada string byte mentah dan tuliskan ke "Membagi string dan menjaga pembatas sebagai bagian dari potongan string split, bukan sebagai elemen daftar terpisah", lihat stackoverflow.com/questions/62591863/…
Lorenz

Jawaban:

295
>>> re.split('(\W)', 'foo/bar spam\neggs')
['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']
Commodore Jaeger
sumber
22
Itu keren. Saya tidak tahu re.sit melakukannya dengan menangkap grup.
Laurence Gonsalves
16
@Laurence: Ya, ini didokumentasikan: docs.python.org/library/re.html#re.split : "Pisahkan string dengan kemunculan pola. Jika menangkap tanda kurung digunakan dalam pola, maka teks semua grup dalam pola juga dikembalikan sebagai bagian dari daftar yang dihasilkan. "
Vinay Sajip
40
Ini benar-benar tidak terdokumentasi. Saya telah menggunakan Python selama 14 tahun dan baru saja menemukan ini.
smci
19
Apakah ada opsi sehingga output pertandingan grup melekat pada apa pun yang ada di sebelah kiri (atau analog dengan kanan) dari perpecahan? Misalnya, apakah ini dapat dengan mudah dimodifikasi sehingga hasilnya ['foo', '/bar', ' spam', '\neggs']?
ely
3
@ Mr.F Anda mungkin dapat melakukan sesuatu dengan re.sub. Saya ingin berpisah pada persen akhir jadi saya hanya menidurkan dalam karakter ganda dan kemudian membelah, gila tetapi bekerja untuk kasus saya: re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot'))->['5.000%', 'Additional Whatnot']
Kyle James Walker
29

Jika Anda membagi pada baris baru, gunakan splitlines(True).

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

(Bukan solusi umum, tetapi menambahkan ini di sini jika seseorang datang ke sini tidak menyadari metode ini ada.)

Mark Lodato
sumber
12

Solusi no-regex lain yang berfungsi dengan baik pada Python 3

# 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
sumber
10

Jika Anda hanya memiliki 1 pemisah, Anda dapat menggunakan pemahaman daftar:

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

Menambahkan / memisahkan pemisah:

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

Pemisah karena elemennya sendiri:

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
sumber
1
Anda juga dapat menambahkan if xuntuk memastikan bahwa bongkahan yang diproduksi oleh splitmemiliki beberapa konten, yaituresult = [x + sep for x in text.split(sep) if x]
saya khawatir alien
Bagi saya, strip dihapus terlalu banyak dan saya harus menggunakan ini:result = [sep+x for x in data.split(sep)] result[0] = result[0][len(sep):]
scottlittle
9

contoh lain, pisahkan dengan non-alpha-numeric dan simpan separator

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

keluaran:

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

penjelasan

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
sumber
Meskipun, seperti yang dikatakan dokumen , ini setara dengan jawaban yang diterima, saya suka keterbacaan versi ini - meskipun \Wcara yang lebih ringkas untuk mengekspresikannya.
ephsmith
3

Anda juga dapat membagi string dengan array string alih-alih ekspresi reguler, seperti ini:

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
sumber
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
sumber
3

Satu Solusi Malas dan Sederhana

Asumsikan pola regex Anda adalah split_pattern = r'(!|\?)'

Pertama, Anda menambahkan beberapa karakter yang sama dengan pemisah baru, seperti '[potong]'

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

Kemudian Anda membagi pemisah baru, new_string.split('[cut]')

Yilei Wang
sumber
Pendekatan ini cerdas, tetapi akan gagal ketika string asli sudah mengandung [cut]suatu tempat.
Matthijs Kooijman
Ini bisa lebih cepat pada masalah skala besar karena akhirnya menggunakan string.split (), dalam kasus yang re.split () harganya lebih mahal daripada re.sub () dengan string.split () (yang saya tidak tahu).
Lorenz
1

Jika seseorang ingin memisahkan string sambil menjaga separator dengan regex tanpa menangkap grup:

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)

Jika seseorang mengasumsikan bahwa regex dimasukkan ke dalam grup penangkap:

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)

Kedua cara juga akan menghapus grup kosong yang tidak berguna dan menjengkelkan di sebagian besar kasus.

Dmitriy Sintsov
sumber
1

Berikut ini adalah .splitsolusi sederhana yang berfungsi tanpa regex.

Ini adalah jawaban untuk Python split () tanpa menghapus pembatas , jadi tidak persis apa yang diminta posting asli tetapi pertanyaan lain ditutup sebagai duplikat untuk yang ini.

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

Tes acak:

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
atau kepuasan
sumber
regex harus dihindari pada masalah skala besar karena alasan kecepatan, itu sebabnya ini adalah petunjuk yang baik.
Lorenz
0

Saya memiliki masalah serupa yang mencoba untuk memecah jalur file dan berjuang untuk menemukan jawaban yang sederhana. Ini berfungsi untuk saya dan tidak melibatkan harus mengganti pembatas kembali ke teks split:

my_path = 'folder1/folder2/folder3/file1'

import re

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

pengembalian:

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

Conor
sumber
Ini dapat sedikit disederhanakan dengan menggunakan: re.findall('[^/]+/?', my_path)(mis. Membuat trailing slash menjadi opsional dengan menggunakan ?daripada memberikan dua alternatif |.
Matthijs Kooijman
0

Saya menemukan pendekatan berbasis generator ini lebih memuaskan:

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

Ini menghindari kebutuhan untuk mencari tahu regex yang benar, sementara secara teori harus cukup murah. Itu tidak membuat objek string baru dan, mendelegasikan sebagian besar iterasi bekerja ke metode find efisien.

... dan dalam Python 3.8 bisa sesingkat:

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
sumber
0
  1. ganti semua seperator: (\W)denganseperator + new_seperator: (\W;)

  2. dibagi dengan 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
sumber