【问题标题】:Split string at commas except when in bracket environment以逗号分隔字符串,除非在括号环境中
【发布时间】:2015-01-04 16:44:24
【问题描述】:

我想在逗号处拆分 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,这几乎 可以满足我的需要,除了 quoted (") 环境受到保护而不是 {}-delimited 环境。

有什么提示吗?

【问题讨论】:

  • AFAIK Python 不支持正则表达式中的递归。仅供参考,这将 do the job 与 PCRE:(?'braces'\{(?:[^{}]++|\g<braces>)*\})(*SKIP)(*FAIL)|,
  • 这不是你要问的小事......正则表达式没有帮助,因为你需要一个带内存的状态机来匹配封闭的项目......(括号,引号等)跨度>
  • 如果没有递归正则表达式(进行递归的表达式)就无法完成。我认为 Python 现在有一个更新的版本可以做到这一点。有趣的是 Perl 如何来自 Python,Perl 将其抛诸脑后。
  • @sln:“Perl 来自 Python”是什么意思?当 Guido 开始考虑 Python 时,Perl 就已经存在,早在大多数人甚至没有听说过 Python 之前就已经广泛使用,并且在 1.x/early-2.x 时代对 Python 的开发产生了影响。特别是 Python 的 re 引擎直接基于 Perl 的。而且我不确定能够在没有警告的情况下花费指数级时间在正则表达式上算作“将其留在尘土中”......
  • @abarnert - 我认为 Perl 可能已经存在,但 Perl 采用了很多功能,这是我不知道的第一个功能。我简要地认为我在 Python re beta 网站上阅读了一些详细信息,似乎有很多新事物正在使用可用的语法结构。

标签: python regex parsing pyparsing


【解决方案1】:

编写您自己的自定义拆分函数:

 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

【讨论】:

  • 干得好... :) 这是目前唯一正确的答案 afaik
  • 很好,但是这个实现的假设是字符串中没有可能不属于分组的“{”或“}”字符。即 ":-}" 如果这种可能性存在,则需要考虑如何处理它。
【解决方案2】:

在这种情况下,您可以使用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.']
>>>

下面是正则表达式匹配的解释:

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

【讨论】:

  • 对于更复杂的嵌套括号示例,这不会失败吗?例如。 "{J. Doe, R. Starr {x,{y}}}, {Lorem {i}psum dolor }, Dol. sit., am. et."?
  • @ajcr - 是的,它会失败。但这就是为什么我说“在这种情况下”。我给出的模式不是防弹的,只能处理简单的字符串。具体来说,它适用于没有带逗号的嵌套花括号的字符串,如 OP 的示例中所示。但是,如果 OP 正在处理如此复杂的字符串,最好放弃正则表达式并构建一个解析器。
  • 我认为您不能将其概括为任何级别的解决方案。
  • 如果您愿意接受对更复杂的情况不起作用的快速破解方法,为什么不选择最简单的方法呢?您实际上并不需要处理匹配的开括号和闭括号;只需将左大括号和右大括号都视为等效的替代“引号”字符,并以与 PyParsing 或 csv 或其他任何方式相同的方式跳过“引号”内的任何逗号?
【解决方案3】:

Lucas Trzesniewski's comment 实际上可以在 Python 中与PyPi regex module 一起使用(我只是将命名组替换为一个编号以使其更短):

>>> 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&lt;1&gt;)*})(*SKIP)(*FAIL) - 匹配 {...{...{}...}...} 类似结构(因为 { 匹配 {(?:[^{}]++|\g&lt;1&gt;)* 匹配 0+ 个出现的 2 个替代项:1)除 { 和 @ 之外的任何 1+ 个字符987654330@([^{}]++),2)文本匹配整个({(?:[^{}]++|\g&lt;1&gt;)*})子模式)。 (*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.']

【讨论】:

    猜你喜欢
    • 2020-04-26
    • 2016-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-14
    相关资源
    最近更新 更多