Разделить строку на запятые, кроме случаев, когда они заключены в скобки

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

{J. Doe, R. Starr}, {Lorem
{i}psum dolor }, Dol. sit., am. et.

Должен быть разделен на

['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']

Это включает в себя сопоставление скобок, поэтому, вероятно, здесь не помогают регулярные выражения. PyParsing имеет commaSeparatedList который почти делает то, что мне нужно, кроме того, что цитируется (") среда защищена вместо {} -ограниченные.

Есть намеки?

3 ответа

Решение

Напишите свою собственную функцию разделения:

 input_string = """{J. Doe, R. Starr}, {Lorem
 {i}psum dolor }, Dol. sit., am. et."""


 expected = ['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']

 def split(s):
     parts = []
     bracket_level = 0
     current = []
     # trick to remove special-case of trailing chars
     for c in (s + ","):
         if c == "," and bracket_level == 0:
             parts.append("".join(current))
             current = []
         else:
             if c == "{":
                 bracket_level += 1
             elif c == "}":
                 bracket_level -= 1
             current.append(c)
     return parts

 assert split(input_string), expected

Ты можешь использовать re.split в этом случае:

>>> from re import split
>>> data = '''\
... {J. Doe, R. Starr}, {Lorem
... {i}psum dolor }, Dol. sit., am. et.'''
>>> split(',\s*(?![^{}]*\})', data)
['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']
>>>

Ниже приведено объяснение того, чему соответствует шаблон Regex:

,       # Matches ,
\s*     # Matches zero or more whitespace characters
(?!     # Starts a negative look-ahead assertion
[^{}]*  # Matches zero or more characters that are not { or }
\}      # Matches }
)       # Closes the look-ahead assertion

Комментарий Лукаса Тресневского можно использовать в Python с модулем регулярных выражений PyPi (я просто заменил именованную группу на пронумерованную, чтобы сделать ее короче):

>>> import regex
>>> r = regex.compile(r'({(?:[^{}]++|\g<1>)*})(*SKIP)(*FAIL)|\s*,\s*')
>>> s = """{J. Doe, R. Starr}, {Lorem
{i}psum dolor }, Dol. sit., am. et."""
>>> print(r.split(s))
['{J. Doe, R. Starr}', None, '{Lorem\n{i}psum dolor }', None, 'Dol. sit.', None, 'am. et.']

Шаблон - ({(?:[^{}]++|\g<1>)*})(*SKIP)(*FAIL) - Матчи {...{...{}...}...} как структуры (как { Матчи {, (?:[^{}]++|\g<1>)* соответствует 0+ появлений 2 альтернатив: 1) любые символы 1+, кроме { а также } ([^{}]++), 2) текст, соответствующий целому ({(?:[^{}]++|\g<1>)*}) подшаблон). (*SKIP)(*FAIL) глаголы заставляют механизм опускать все совпадающее значение из буфера совпадения, таким образом, перемещая индекс в конец совпадения и не удерживая ничего для возврата (мы "пропускаем" то, что сопоставили).

\s*,\s* соответствует запятой, заключенной в 0+ пробелов.

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

>>> print([x for x in r.split(s) if x])
['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']
Другие вопросы по тегам