【问题标题】:Splitting comma delimited strings in python在python中拆分逗号分隔的字符串
【发布时间】:2014-01-03 03:41:36
【问题描述】:

这个问题已经被问过很多次了。一些例子:[1][2]。但似乎没有更普遍的东西。我正在寻找的是一种以逗号分隔不在引号或分隔符对内的字符串的方法。例如:

s1 = 'obj<1, 2, 3>, x(4, 5), "msg, with comma"'

应该分成三个元素的列表

['obj<1, 2, 3>', 'x(4, 5)', '"msg, with comma"']

现在的问题是这会变得更加复杂,因为我们可以查看成对的 &lt;&gt;()

s2 = 'obj<1, sub<6, 7>, 3>, x(4, y(8, 9), 5), "msg, with comma"'

应该拆分成:

['obj<1, sub<6, 7>, 3>', 'x(4, y(8, 9), 5)', '"msg, with comma"']

不使用正则表达式的天真解决方案是通过查找字符,&lt;( 来解析字符串。如果找到&lt;(,则我们开始计算奇偶校验。如果奇偶校验为零,我们只能以逗号分隔。比如说我们要拆分s2,我们可以从parity = 0开始,当我们到达s2[3]时,我们遇到&lt;,这将使奇偶校验增加1。奇偶校验只有在遇到&gt;或@时才会减少987654341@,遇到&lt;(会增加。虽然奇偶校验不为 0,但我们可以简单地忽略逗号而不进行任何拆分。

这里的问题是,有没有办法用正则表达式快速做到这一点?我真的在研究这个solution,但这似乎并不涵盖我给出的例子。

更通用的函数是这样的:

def split_at(text, delimiter, exceptions):
    """Split text at the specified delimiter if the delimiter is not
    within the exceptions"""

有些用途是这样的:

split_at('obj<1, 2, 3>, x(4, 5), "msg, with comma"', ',', [('<', '>'), ('(', ')'), ('"', '"')]

正则表达式是否能够处理这个问题,或者是否有必要创建一个专门的解析器?

【问题讨论】:

  • 在这种情况下,正则表达式对您没有帮助,因为您尝试解析的语言(即字符串组)不规则。鉴于您允许标签的任意嵌套,没有简单的方法可以通过正则表达式摆脱这种情况。
  • Regex 实际上不能处理这个问题,你也不想这样。复杂性至少是线性的,因此您必须始终使用奇偶校验检查器获得更好的性能。不过,您不必自己构建它。 Python 的 csv 模块做了很多工作。
  • 啊,不要说正则表达式不能处理它!也许 python 风味不能,但其他风味如 PCRE 可以做到!这是a proof,我们甚至可能会花哨并使用递归模式来考虑嵌套的&lt;&gt;()
  • 如果您知道括号内元素的最大嵌套递归深度,也可以使用正则表达式。但是在没有 recursive regex support 的 Python 中,使用更易于维护的解析器函数。
  • Aaaand I did it,现在的问题是我为什么要 O_o ?

标签: python regex string split


【解决方案1】:

虽然不能使用正则表达式,但下面的简单代码将达到预期的效果:

def split_at(text, delimiter, opens='<([', closes='>)]', quotes='"\''):
    result = []
    buff = ""
    level = 0
    is_quoted = False

    for char in text:
        if char in delimiter and level == 0 and not is_quoted:
            result.append(buff)
            buff = ""
        else:
            buff += char

            if char in opens:
                level += 1
            if char in closes:
                level -= 1
            if char in quotes:
                is_quoted = not is_quoted

    if not buff == "":
        result.append(buff)

    return result

在解释器中运行:

>>> split_at('obj<1, 2, 3>, x(4, 5), "msg, with comma"', ',')                                                                                                                                 
#=>['obj<1, 2, 3>', ' x(4, 5)', ' "msg with comma"']

【讨论】:

  • if char in closes: level -= 1 continue if char in opens: 这应该可以让您添加打开和关闭的分隔符,例如文字引号。所以"msg, with comma" 通过。这种情况不需要单独的处理程序。
【解决方案2】:

使用迭代器和生成器:

def tokenize(txt, delim=',', pairs={'"':'"', '<':'>', '(':')'}):
    fst, snd = set(pairs.keys()), set(pairs.values())
    it = txt.__iter__()

    def loop():
        from collections import defaultdict
        cnt = defaultdict(int)

        while True:
            ch = it.__next__()
            if ch == delim and not any (cnt[x] for x in snd):
                return
            elif ch in fst:
                cnt[pairs[ch]] += 1
            elif ch in snd:
                cnt[ch] -= 1
            yield ch

    while it.__length_hint__():
        yield ''.join(loop())

和,

>>> txt = 'obj<1, sub<6, 7>, 3>,x(4, y(8, 9), 5),"msg, with comma"'
>>> [x for x in tokenize(txt)]
['obj<1, sub<6, 7>, 3>', 'x(4, y(8, 9), 5)', '"msg, with comma"']

【讨论】:

    【解决方案3】:

    如果您有递归嵌套表达式,您可以拆分逗号并验证它们是否与 pyparsing 匹配:

    import pyparsing as pp
    
    def CommaSplit(txt):
        ''' Replicate the function of str.split(',') but do not split on nested expressions or in quoted strings'''
        com_lok=[]
        comma = pp.Suppress(',')
        # note the location of each comma outside an ignored expression:
        comma.setParseAction(lambda s, lok, toks: com_lok.append(lok))
        ident = pp.Word(pp.alphas+"_", pp.alphanums+"_")  # python identifier
        ex1=(ident+pp.nestedExpr(opener='<', closer='>'))   # Ignore everthing inside nested '< >'
        ex2=(ident+pp.nestedExpr())                       # Ignore everthing inside nested '( )'
        ex3=pp.Regex(r'("|\').*?\1')                      # Ignore everything inside "'" or '"'
        atom = ex1 | ex2 | ex3 | comma
        expr = pp.OneOrMore(atom) + pp.ZeroOrMore(comma  + atom )
        try:
            result=expr.parseString(txt)
        except pp.ParseException:
            return [txt]
        else:    
            return [txt[st:end] for st,end in zip([0]+[e+1 for e in com_lok],com_lok+[len(txt)])]             
    
    
    tests='''\
    obj<1, 2, 3>, x(4, 5), "msg, with comma"
    nesteobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), "msg, with comma"
    nestedobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), 'msg, with comma', additional<1, sub<6, 7>, 3>
    bare_comma<1, sub(6, 7), 3>, x(4, y(8, 9), 5),  , 'msg, with comma', obj<1, sub<6, 7>, 3>
    bad_close<1, sub<6, 7>, 3), x(4, y(8, 9), 5), 'msg, with comma', obj<1, sub<6, 7>, 3)
    '''
    
    for te in tests.splitlines():
        result=CommaSplit(te)
        print(te,'==>\n\t',result)
    

    打印:

    obj<1, 2, 3>, x(4, 5), "msg, with comma" ==>
         ['obj<1, 2, 3>', ' x(4, 5)', ' "msg, with comma"']
    nesteobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), "msg, with comma" ==>
         ['nesteobj<1, sub<6, 7>, 3>', ' nestedx(4, y(8, 9), 5)', ' "msg, with comma"']
    nestedobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), 'msg, with comma', additional<1, sub<6, 7>, 3> ==>
         ['nestedobj<1, sub<6, 7>, 3>', ' nestedx(4, y(8, 9), 5)', " 'msg, with comma'", ' additional<1, sub<6, 7>, 3>']
    bare_comma<1, sub(6, 7), 3>, x(4, y(8, 9), 5),  , 'msg, with comma', obj<1, sub<6, 7>, 3> ==>
         ['bare_comma<1, sub(6, 7), 3>', ' x(4, y(8, 9), 5)', '  ', " 'msg, with comma'", ' obj<1, sub<6, 7>, 3>']
    bad_close<1, sub<6, 7>, 3), x(4, y(8, 9), 5), 'msg, with comma', obj<1, sub<6, 7>, 3) ==>
         ["bad_close<1, sub<6, 7>, 3), x(4, y(8, 9), 5), 'msg, with comma', obj<1, sub<6, 7>, 3)"]
    

    当前行为就像'(something does not split), b, "in quotes", c'.split',') 一样,包括保留前导空格和引号。从字段中去除引号和前导空格是微不足道的。

    try下的else改为:

    else:
        rtr = [txt[st:end] for st,end in zip([0]+[e+1 for e in com_lok],com_lok+[len(txt)])]
        if strip_fields:
            rtr=[e.strip().strip('\'"') for e in rtr]
        return rtr  
    

    【讨论】:

    • 这种方法的缺点是您必须构建条件来重新缝合不应该拆分的项目。
    • 这是不正确的,因为它拆分了字符串"obj&lt;1, 2, 3&gt;"
    • 我同意图书馆是明智的解决方案,但这并不能正确回答问题。
    • 我修正了结果。谢谢。
    • 可能需要考虑另一种修复方法,因为以下方法不起作用:result=expr.parseString('obj&lt;1, sub&lt;6, 7&gt;, 3&gt;,x(4, y(8, 9), 5),"msg, with comma"')
    猜你喜欢
    • 2018-12-21
    • 2015-03-07
    • 1970-01-01
    • 2023-01-03
    • 2017-01-05
    • 2012-05-24
    相关资源
    最近更新 更多