Подтвердить что ты не робот

Эффективно разбить строку на несколько разделителей и сохранить каждый разделитель?

Мне нужно разбить строки данных, используя каждый символ из string.punctuation и string.whitespace в качестве разделителя.

Кроме того, мне нужно, чтобы разделители оставались в выходном списке, между элементами, которые они разделяли в строке.

Например,

"Now is the winter of our discontent"

должен выводиться:

['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent']

Я не уверен, как это сделать, не прибегая к оргии вложенных циклов, что неприемлемо медленно. Как я могу это сделать?

4b9b3361

Ответ 1

Другой подход, не относящийся к регулярному выражению от других:

>>> import string
>>> from itertools import groupby
>>> 
>>> special = set(string.punctuation + string.whitespace)
>>> s = "One two  three    tab\ttabandspace\t end"
>>> 
>>> split_combined = [''.join(g) for k, g in groupby(s, lambda c: c in special)]
>>> split_combined
['One', ' ', 'two', '  ', 'three', '    ', 'tab', '\t', 'tabandspace', '\t ', 'end']
>>> split_separated = [''.join(g) for k, g in groupby(s, lambda c: c if c in special else False)]
>>> split_separated
['One', ' ', 'two', '  ', 'three', '    ', 'tab', '\t', 'tabandspace', '\t', ' ', 'end']

Могу использовать dict.fromkeys и .get вместо lambda, я думаю.

[править]

Некоторое объяснение:

groupby принимает два аргумента, итерабельную и (необязательную) ключевую функцию. Он перемещается по итерируемому и группирует их со значением ключевой функции:

>>> groupby("sentence", lambda c: c in 'nt')
<itertools.groupby object at 0x9805af4>
>>> [(k, list(g)) for k,g in groupby("sentence", lambda c: c in 'nt')]
[(False, ['s', 'e']), (True, ['n', 't']), (False, ['e']), (True, ['n']), (False, ['c', 'e'])]

где слагаемые со смежными значениями ключевой функции группируются вместе. (Это общий источник ошибок, на самом деле - люди забывают, что сначала они должны сортировать по keyfunc, если они хотят сгруппировать термины, которые могут быть не последовательными.)

Как предположил @JonClements, я имел в виду

>>> special = dict.fromkeys(string.punctuation + string.whitespace, True)
>>> s = "One two  three    tab\ttabandspace\t end"
>>> [''.join(g) for k,g in groupby(s, special.get)]
['One', ' ', 'two', '  ', 'three', '    ', 'tab', '\t', 'tabandspace', '\t ', 'end']

для случая, когда мы объединяли сепараторы. .get возвращает None, если значение не указано в dict.

Ответ 2

import re
import string

p = re.compile("[^{0}]+|[{0}]+".format(re.escape(
    string.punctuation + string.whitespace)))

print p.findall("Now is the winter of our discontent")

Я не большой поклонник использования регулярных выражений для всех проблем, но я не думаю, что у вас есть выбор в этом, если вы хотите быстро и коротко.

Я объясню regexp, так как вы не знакомы с ним:

  • [...] означает любой из символов внутри квадратных скобок
  • [^...] означает любой из символов, не входящих в квадратные скобки
  • + позади означает одну или несколько предыдущих вещей
  • x|y означает соответствие либо x, либо y

Таким образом, регулярное выражение соответствует одному или нескольким символам, где либо все должно быть пунктуацией и пробелом, либо не должно быть. Метод findall находит все совпадающие совпадения шаблона.

Ответ 3

Попробуйте следующее:

import re
re.split('(['+re.escape(string.punctuation + string.whitespace)+']+)',"Now is the winter of our discontent")

Объяснение из документации Python:

Если в шаблоне используются скобки для скобок, тогда текст всех групп в шаблоне также возвращается как часть результирующего списка.

Ответ 4

Решение в линейном (O(n)) времени:

Скажем, у вас есть строка:

original = "a, b...c    d"

Сначала преобразуйте все разделители в пространство:

splitters = string.punctuation + string.whitespace
trans = string.maketrans(splitters, ' ' * len(splitters))
s = original.translate(trans)

Теперь s == 'a b c d'. Теперь вы можете использовать itertools.groupby для чередования между пробелами и не-пробелами:

result = []
position = 0
for _, letters in itertools.groupby(s, lambda c: c == ' '):
    letter_count = len(list(letters))
    result.append(original[position:position + letter_count])
    position += letter_count

Теперь result == ['a', ', ', 'b', '...', 'c', ' ', 'd'], что вам нужно.

Ответ 5

В зависимости от текста, с которым вы имеете дело, вы можете упростить свою концепцию разделителей на "что угодно, кроме букв и цифр". Если это будет работать, вы можете использовать следующее регулярное выражение:

re.findall(r'[a-zA-Z\d]+|[^a-zA-Z\d]', text)

Это предполагает, что вы хотите разделить на каждый отдельный символ разделителя, даже если они происходят последовательно, поэтому 'foo..bar' станет ['foo', '.', '.', 'bar']. Если вместо этого вы ожидаете ['foo', '..', 'bar'], используйте [a-zA-Z\d]+|[^a-zA-Z\d]+ (только разница добавляет + в самом конце).

Ответ 6

Мой прием:

from string import whitespace, punctuation
import re

pattern = re.escape(whitespace + punctuation)
print re.split('([' + pattern + '])', 'now is the winter of')

Ответ 7

from string import punctuation, whitespace

s = "..test. and stuff"

f = lambda s, c: s + ' ' + c + ' ' if c in punctuation else s + c
l =  sum([reduce(f, word).split() for word in s.split()], [])

print l

Ответ 8

Для любого произвольного набора разделителей:

def separate(myStr, seps):
    answer = []
    temp = []
    for char in myStr:
        if char in seps:
            answer.append(''.join(temp))
            answer.append(char)
            temp = []
        else:
            temp.append(char)
    answer.append(''.join(temp))
    return answer

In [4]: print separate("Now is the winter of our discontent", set(' '))
['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent']

In [5]: print separate("Now, really - it is the winter of our discontent", set(' ,-'))
['Now', ',', '', ' ', 'really', ' ', '', '-', '', ' ', 'it', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent']

Надеюсь, что это поможет

Ответ 9

from itertools import chain, cycle, izip

s = "Now is the winter of our discontent"
words = s.split()

wordsWithWhitespace = list( chain.from_iterable( izip( words, cycle([" "]) ) ) )
# result : ['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent', ' ']