【问题标题】:How to split a string by commas positioned outside of parenthesis?如何用括号外的逗号分割字符串?
【发布时间】:2010-12-11 12:52:27
【问题描述】:

我得到了这样格式的字符串:

"Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"

所以基本上它是演员姓名的列表(可选地在括号中加上他们的角色)。角色本身可以包含逗号(演员的名字不能,我强烈希望如此)。

我的目标是将此字符串拆分为一对列表 - (actor name, actor role)

一个明显的解决方案是遍历每个字符,检查'('')'',' 的出现情况,并在出现逗号外部时将其拆分。不过这个好像有点重……

我正在考虑使用正则表达式拆分它:首先用括号拆分字符串:

import re
x = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
s = re.split(r'[()]', x) 
# ['Wilbur Smith ', 'Billy, son of John', ', Eddie Murphy ', 'John', ', Elvis Presley, Jane Doe ', 'Jane Doe', '']

这里奇怪的元素是演员的名字,甚至是角色。然后我可以用逗号分隔名称并以某种方式提取名称-角色对。但这似乎比我的第一种方法更糟糕。

有没有更简单/更好的方法来做到这一点,无论是使用单个正则表达式还是一段漂亮的代码?

【问题讨论】:

    标签: python regex split


    【解决方案1】:

    一种方法是将findall 与一个正则表达式一起使用,该表达式贪婪地匹配可以在分隔符之间移动的内容。例如:

    >>> s = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
    >>> r = re.compile(r'(?:[^,(]|\([^)]*\))+')
    >>> r.findall(s)
    ['Wilbur Smith (Billy, son of John)', ' Eddie Murphy (John)', ' Elvis Presley', ' Jane Doe (Jane Doe)']
    

    上面的正则表达式匹配一个或多个:

    • 非逗号、非开括号字符
    • 以开括号开头、包含 0 个或多个非闭括号,然后是闭括号的字符串

    这种方法的一个怪癖是相邻的分隔符被视为单个分隔符。也就是说,您不会看到空字符串。根据您的用例,这可能是错误或功能。

    另请注意,正则表达式适用于可能存在嵌套的情况。因此,例如,这会错误地拆分:

    "Wilbur Smith (son of John (Johnny, son of James), aka Billy), Eddie Murphy (John)"
    

    如果您需要处理嵌套,最好的办法是将字符串划分为括号、逗号和其他任何内容(本质上是对其进行标记——这部分仍然可以使用正则表达式完成),然后遍历这些标记重新组装字段,随时跟踪您的嵌套级别(这种跟踪嵌套级别是正则表达式自己无法做到的)。

    【讨论】:

    • 您可以通过匹配记录而不是分隔符来立即拆分为字段: [(m.group("name"), m.group("role")) for m in re.findall(" (?P.+?)( ?((?P[^)]+))(,\s*|$))", x)]
    • 如果他需要令牌解决方案,则 +1。在您上下行走时弹出和弹出堆栈......这是一种经典的方式。
    • 每次我看到有用的正则表达式,比如这个,我开始怀疑——它们应该是人类可读的吗?还是只有我……谁没有第一眼看到它?
    • 很好的答案。谢谢劳伦斯!如果我想这样做但在输出中包含括号内的内容,我该如何解决这个问题?
    • @user815423426 在这种情况下我可能会使用的方法是对结果列表进行第二次传递并删除括号。例如:r = [re.sub(r'\([^)]*\)', '', s) for s in r] 之类的东西。如果您需要更详细的答案,您可能应该发布一个单独的问题。
    【解决方案2】:
    s = re.split(r',\s*(?=[^)]*(?:\(|$))', x) 
    

    lookahead 匹配直到下一个左括号或字符串末尾的所有内容,iff 之间没有右括号。这样可以确保逗号不在一组括号内。

    【讨论】:

      【解决方案3】:

      我认为解决此问题的最佳方法是使用 python 的内置 csv 模块。

      由于 csv 模块只有 allows 一个字符 quotechar,因此您需要对输入进行替换以将 () 转换为 |" 之类的内容。然后确保您使用的是适当的方言,然后离开。

      【讨论】:

        【解决方案4】:

        人类可读的正则表达式的尝试:

        import re
        
        regex = re.compile(r"""
            # name starts and ends on word boundary
            # no '(' or commas in the name
            (?P<name>\b[^(,]+\b)
            \s*
            # everything inside parentheses is a role
            (?:\(
              (?P<role>[^)]+)
            \))? # role is optional
            """, re.VERBOSE)
        
        s = ("Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley,"
             "Jane Doe (Jane Doe)")
        print re.findall(regex, s)
        

        输出:

        [('Wilbur Smith', 'Billy, son of John'), ('Eddie Murphy', 'John'), 
         ('Elvis Presley', ''), ('Jane Doe', 'Jane Doe')]
        

        【讨论】:

          【解决方案5】:

          我的回答不会使用正则表达式。

          我认为状态为“in_actor_name”的简单字符扫描器应该可以工作。请记住,状态“in_actor_name”在此状态下由 ')' 或逗号终止。

          我的尝试:

          s = 'Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)'
          
          in_actor_name = 1
          role = ''
          name = ''
          for c in s:
              if c == ')' or (c == ',' and in_actor_name):
                  in_actor_name = 1
                  name = name.strip()
                  if name:
                      print "%s: %s" % (name, role)
                  name = ''
                  role = ''
              elif c == '(':
                  in_actor_name = 0
              else:
                  if in_actor_name:
                      name += c
                  else:
                      role += c
          if name:
              print "%s: %s" % (name, role)
          

          输出:

          Wilbur Smith: Billy, son of John
          Eddie Murphy: John
          Elvis Presley: 
          Jane Doe: Jane Doe
          

          【讨论】:

            【解决方案6】:

            这是我过去在此类情况下使用的一般技术:

            re 模块的sub 函数与函数一起用作替换参数。该函数跟踪左括号、括号和大括号,以及单引号和双引号,并且仅在这些带括号和引用的子字符串之外执行替换。然后,您可以将非括号/引号逗号替换为您确定不会出现在字符串中的另一个字符(我使用 ASCII/Unicode 组分隔符:chr(29) 代码),然后执行一个简单的字符串。分裂那个角色。代码如下:

            import re
            def srchrepl(srch, repl, string):
                """Replace non-bracketed/quoted occurrences of srch with repl in string"""
            
                resrchrepl = re.compile(r"""(?P<lbrkt>[([{])|(?P<quote>['"])|(?P<sep>["""
                                        + srch + """])|(?P<rbrkt>[)\]}])""")
                return resrchrepl.sub(_subfact(repl), string)
            
            def _subfact(repl):
                """Replacement function factory for regex sub method in srchrepl."""
                level = 0
                qtflags = 0
                def subf(mo):
                    nonlocal level, qtflags
                    sepfound = mo.group('sep')
                    if  sepfound:
                        if level == 0 and qtflags == 0:
                            return repl
                        else:
                            return mo.group(0)
                    elif mo.group('lbrkt'):
                        level += 1
                        return mo.group(0)
                    elif mo.group('quote') == "'":
                        qtflags ^= 1            # toggle bit 1
                        return "'"
                    elif mo.group('quote') == '"':
                        qtflags ^= 2            # toggle bit 2
                        return '"'
                    elif mo.group('rbrkt'):
                        level -= 1
                        return mo.group(0)
                return subf
            

            如果您的 Python 版本中没有 nonlocal,只需将其更改为 global 并在模块级别定义 levelqtflags

            它的使用方法如下:

            >>> GRPSEP = chr(29)
            >>> string = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
            >>> lst = srchrepl(',', GRPSEP, string).split(GRPSEP)
            >>> lst
            ['Wilbur Smith (Billy, son of John)', ' Eddie Murphy (John)', ' Elvis Presley', ' Jane Doe (Jane Doe)']
            

            【讨论】:

              【解决方案7】:

              这篇文章对我帮助很大。我正在寻找用引号外的逗号分隔字符串。我用这个作为首发。我的最后一行代码是regEx = re.compile(r'(?:[^,"]|"[^"]*")+') 这成功了。非常感谢。

              【讨论】:

                【解决方案8】:

                我当然同意上面的@Wogan,即使用 CSV moudle 是一个好方法。话虽如此,如果您仍想尝试正则表达式解决方案,请尝试一下,但您必须使其适应 Python 方言

                string.split(/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/)
                

                HTH

                【讨论】:

                  【解决方案9】:

                  用“)”分割

                  >>> s="Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
                  >>> s.split(")")
                  ['Wilbur Smith (Billy, son of John', ', Eddie Murphy (John', ', Elvis Presley, Jane Doe (Jane Doe', '']
                  >>> for i in s.split(")"):
                  ...   print i.split("(")
                  ...
                  ['Wilbur Smith ', 'Billy, son of John']
                  [', Eddie Murphy ', 'John']
                  [', Elvis Presley, Jane Doe ', 'Jane Doe']
                  ['']
                  

                  您可以进一步检查以获取那些不带 () 的名称。

                  【讨论】:

                    【解决方案10】:

                    如果您的数据中有任何错误或噪音,上述答案均不正确。

                    如果您知道数据每次都是正确的,就很容易想出一个好的解决方案。但是如果出现格式错误怎么办?你希望发生什么?

                    假设有嵌套括号?假设有不匹配的括号?假设字符串以逗号结尾或以逗号开头,或者连续两个?

                    以上所有解决方案都会产生或多或少的垃圾,并且不会向您报告。

                    如果由我决定,我会从对“正确”数据的非常严格的限制开始 - 没有嵌套括号,没有不匹配的括号,并且在 cmets 之前、之间或之后没有空段 - 在我去的时候验证,如果我无法验证,则引发异常。

                    【讨论】:

                    • 我们必须假设问题包含我们需要回答的所有信息。因此,我们假设输入已经被验证并且格式已经被完整描述(例如,没有嵌套括号)。如果这些假设中的任何一个被证明是错误的,人们希望 OP 将来能够学会提出更好的问题。 ;)
                    猜你喜欢
                    • 1970-01-01
                    • 2020-04-26
                    • 2017-04-07
                    • 2021-06-12
                    • 2018-02-15
                    • 2020-08-30
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多