2013-12-15 127 views
9

此问题已被询问和回答多次。一些例子:[1],[2]。但似乎并没有一些更一般的东西。我正在寻找的是一种用逗号分隔字符串的方式,这些逗号不在引号或分隔符对之内。例如:在python中分割逗号分隔的字符串

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

应分成三个要素

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

的问题的列表,现在是这样可以变得更加复杂,因为我们可以考虑对<>()

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"'] 

不使用正则表达式的幼稚的解决方案是通过寻找字符,<(解析字符串。如果找到<(,那么我们开始计数奇偶校验。如果奇偶性为零,我们只能以逗号分割。举例说,我们要拆分s2,我们可以parity = 0开始,当我们到达s2[3]我们遇到<这将增加1奇偶校验时遇到>)奇偶只会减少,当它遇到它会增加<( 。虽然奇偶校验不是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"', ',', [('<', '>'), ('(', ')'), ('"', '"')] 

会的正则表达式能够处理这个还是有必要建立一个专门的解析器?

+0

正则表达式不会帮助你在这种情况下,由于语言(即字符串组)你试图解析是不规则的。考虑到你允许任意嵌套标签,没有简单的方法来将你的出路解决。 –

+1

正则表达式实际上不能处理这个,你不会想要它。复杂性至少是线性的,所以用奇偶校验器必然会获得更好的性能。尽管如此,你不必自己构建它。 Python的'csv'模块做了很多工作。 –

+2

啊,不要说那个正则表达式无法处理它!也许蟒蛇的味道不能,但像PCRE这样的其他口味可以做到!这是[证明](http://regex101.com/r/wU7lC9),我们甚至可能会喜欢并使用递归模式来考虑嵌套的'<>()' – HamZa

回答

8

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

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"'] 
+0

'如果字符在关闭:级别= 1继续如果字符打开:'这应该让你添加分隔符,打开和关闭,如字面引号。所以'“msg,用逗号”'传递。这种情况下不需要分离处理程序。 – kalhartt

4

如果你有递归嵌套表达式,你可以在逗号分割并验证它们是否与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',')包括保持前导空格和引号。从田间剥去报价和领先空间是微不足道的。

更改elsetry到:使用迭代器和发电机

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 
+0

这个方法的缺点是你必须建立条件来重新缝合那些不应该被拆分的物品。 – brandonscript

+1

这是不正确的,因为它将字符串“”obj <1, 2, 3>“'分开。 – jmlopez

+0

+1指向一个库,而不是滚动你自己的 –

5

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"'] 
相关问题