regular - Em Python, como dividir uma string e manter os separadores?



python 3 regex split (7)

Aqui está a maneira mais simples de explicar isso. Aqui está o que estou usando:

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

Aqui está o que eu quero:

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

A razão é que eu quero dividir uma string em tokens, manipulá-la e juntá-la novamente.

https://ffff65535.com


Eu tive um problema semelhante ao tentar dividir um caminho de arquivo e lutei para encontrar uma resposta simples. Isso funcionou para mim e não envolveu a necessidade de substituir os delimitadores pelo texto dividido:

my_path = 'folder1/folder2/folder3/file1'

import re

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

retorna:

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


Se alguém quiser dividir a string mantendo os separadores por regex sem capturar o grupo:

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)

Se assumirmos que o regex é empacotado no grupo de captura:

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)

Ambas as formas também removerão grupos vazios que são inúteis e irritantes na maioria dos casos.


Se você tiver apenas um separador, poderá usar as compreensões da lista:

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

Acrescentar / separar prepending:

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

Separador como elemento próprio:

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

Uma solução preguiçosa e simples

Suponha que seu padrão de regex seja split_pattern = r'(!|\?)'

Primeiro, você adiciona um caractere igual ao novo separador, como "[corte]"

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

Então você divide o novo separador, new_string.split('[cut]')


outro exemplo, dividido em não-alfanumérico e manter os separadores

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

saída:

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

explicação

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

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

Outra solução sem regex que funciona bem no 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, '<'))

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




regex