Python'da bir dizeyi nasıl bölerim ve ayırıcıları nasıl saklarım?


226

İşte bunu açıklamanın en basit yolu. İşte ne kullanıyorum:

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

İşte istediğim:

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

Bunun nedeni, bir dizgiyi jetonlara bölmek, manipüle etmek, sonra tekrar bir araya getirmek istiyorum.


3
ne anlama geliyor \W? Google'da başarısız oldum.
Ooker

8
Bir sözcük olmayan karakter detayları için buraya bakınız
Russell

Ham bayt dizesine uygulanan ve "Bir dizeyi böl ve ayırıcıları ayrı liste öğeleri olarak değil, bölünmüş dizgi parçalarının bir parçası olarak sakla" ya koymak için bkz. Stackoverflow.com/questions/62591863/…
Lorenz

Yanıtlar:


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

22
Çok havalı. Re.split'in bunu yakalama gruplarıyla yaptığını bilmiyordum.
Laurence Gonsalves

16
@Laurence: Evet, belgelenmiştir: docs.python.org/library/re.html#re.split : " Dizgiyi kalıp oluşumlarına göre ayır . Eğer desende yakalama parantezleri kullanılıyorsa, kalıptaki tüm grupların metni sonuç listesinin bir parçası olarak da döndürülür. "
Vinay Sajip

40
Ciddi derecede belgelenmemiş. 14 yıldır Python kullanıyorum ve sadece bunu öğrendim.
smci

19
Grup eşleşmesinin çıktısının, bölünmenin solunda (veya benzer şekilde sağında) herhangi bir şeye iliştirilmesi için bir seçenek var mı? Örneğin, çıktı kolayca değiştirilebilecek şekilde değiştirilebilir ['foo', '/bar', ' spam', '\neggs']mi?
ely

3
@ Mr.F re.sub ile bir şeyler yapabilirsiniz. Ben biten bir yüzde ayrılmak istedim, bu yüzden ben sadece bir çift karakter subbed ve sonra bölünmüş, hacky ama benim durumum için çalıştı: re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot'))->['5.000%', 'Additional Whatnot']
Kyle James Walker

29

Yeni satıra bölüyorsanız kullanın splitlines(True).

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

(Genel bir çözüm değil, ancak birisinin buraya gelmesi durumunda bu yöntemin var olduğunu fark etmemesi durumunda bunu eklemek.)


12

Python 3'te iyi çalışan başka bir normal olmayan çözüm

# 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, '<'))

10

Yalnızca 1 ayırıcınız varsa, liste kavramalarını kullanabilirsiniz:

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

Ayırıcı ekleme / ekleme:

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

Kendi elemanı olarak ayırıcı:

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

1
Ayrıca ekleyebilirsiniz if xtarafından üretilen yığın sağlamak için splitbazı içerikleri, yani varresult = [x + sep for x in text.split(sep) if x]
i yabancı alarma

Benim için, şerit çok kaldırıldı ve bunu kullanmak zorunda kaldı:result = [sep+x for x in data.split(sep)] result[0] = result[0][len(sep):]
scottlittle

9

başka bir örnek, alfasayısal olmayanlara ayırın ve ayırıcıları saklayın

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

çıktı:

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

açıklama

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

() <- keep the separators
[] <- match everything in between
^a-zA-Z0-9 <-except alphabets, upper/lower and numbers.

Dokümanların söylediği gibi, bu kabul edilen cevaba eşdeğer olsa da, bu versiyonun okunabilirliğini seviyorum - \Wifade etmenin daha kompakt bir yolu olsa da.
ephsmith

3

Ayrıca, bir dizeyi normal ifade yerine dizeler dizisiyle bölebilirsiniz, örneğin:

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 = ["'''", '+=', '+', "/", "*", "\\'", '\\"', "-=", "-", " ", '"""', "(", ")"]))

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

3

Bir Tembel ve Basit Çözüm

Normal ifade düzeninizin split_pattern = r'(!|\?)'

İlk olarak, yeni ayırıcıyla aynı karakteri eklersiniz, '[kes]'

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

Sonra yeni ayırıcıyı ayırırsınız, new_string.split('[cut]')


Bu yaklaşım akıllıdır, ancak orijinal dize zaten bir [cut]yerde olduğunda başarısız olur .
Matthijs Kooijman

Re.split () 'nin string.split () (bilmediğim) ile re.sub ()' den daha pahalı olması durumunda, son olarak string.split () kullandığından büyük ölçekli sorunlarda daha hızlı olabilir.
Lorenz

1

Grup yakalamadan ayırıcıları normal ifadeyle tutarken dizeyi bölmek isterseniz:

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)

Eğer regex'in yakalama grubuna sarıldığını varsayarsak:

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)

Her iki yol da çoğu durumda işe yaramaz ve sinir bozucu olan boş grupları kaldıracaktır.


1

İşte .splitregex olmadan çalışan basit bir çözüm.

Bu, sınırlayıcıyı kaldırmadan Python split () için bir cevaptır , bu yüzden orijinal yazının sorduğu tam olarak değil, diğer soru bunun için bir kopya olarak kapatıldı.

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

Rastgele testler:

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

Hız nedeniyle büyük ölçekli problemlerde regex'ten kaçınılmalıdır, bu yüzden bu iyi bir ipucudur.
Lorenz

0

Bir dosya yolunu ayırmaya çalışırken benzer bir sorun yaşadım ve basit bir cevap bulmakta zorlandım. Bu benim için çalıştı ve ayırıcıları bölünmüş metne geri koymak zorunda kalmadı:

my_path = 'folder1/folder2/folder3/file1'

import re

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

İadeler:

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


Bu, aşağıdakiler kullanılarak biraz basitleştirilebilir: re.findall('[^/]+/?', my_path)(örneğin, ?iki eğik çizgi yerine iki alternatif sağlamak yerine kullanarak eğik çizgi isteğe bağlı hale getirilebilir |.
Matthijs Kooijman

0

Bu jeneratör tabanlı yaklaşımı daha tatmin edici buldum:

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

Teoride oldukça ucuz olmalı, doğru regex'i bulma ihtiyacını ortadan kaldırır. Yeni dize nesneleri oluşturmaz ve yineleme çalışmalarının çoğunu etkin bulma yöntemine devreder.

... ve Python 3.8'de şu kadar kısa olabilir:

def split_keep(string, sep):
    start = 0
    while (end := string.find(sep, start) + 1) > 0:
        yield string[start:end]
        start = end
    yield string[start:]

0
  1. tümünü değiştir seperator: (\W)ileseperator + new_seperator: (\W;)

  2. tarafından bölün 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')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.