Разделить строку на запятые, кроме случаев, когда они заключены в скобки
Я хотел бы разбить многострочную строку 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.']