2013-12-15 30 views
9

Câu hỏi này đã được hỏi và trả lời nhiều lần trước đây. Một số ví dụ: [1], [2]. Nhưng dường như không có gì chung chung hơn. Những gì tôi đang tìm kiếm là một cách để tách các chuỗi tại dấu phẩy không nằm trong dấu ngoặc kép hoặc cặp dấu tách. Ví dụ:Tách các chuỗi phân tách bằng dấu phẩy trong python

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

nên được tách ra thành một danh sách ba yếu tố

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

Vấn đề bây giờ là điều này có thể trở nên phức tạp hơn vì chúng ta có thể nhìn vào cặp <>().

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

mà nên được chia thành:

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

Giải pháp ngây thơ mà không sử dụng regex là phải phân tích chuỗi bằng cách tìm kiếm các nhân vật ,<(. Nếu tìm thấy < hoặc ( thì chúng tôi bắt đầu tính tính chẵn lẻ. Chúng tôi chỉ có thể phân tách bằng dấu phẩy nếu số chẵn lẻ bằng 0. Ví dụ nói rằng chúng ta muốn chia s2, chúng ta có thể bắt đầu với parity = 0 và khi chúng ta đạt s2[3] chúng ta gặp phải < mà sẽ tăng chẵn lẻ bằng 1. chẵn lẻ sẽ chỉ giảm khi nó gặp > hoặc ) và nó sẽ tăng khi nó gặp < hoặc ( . Trong khi tính chẵn lẻ không phải là 0, chúng ta có thể đơn giản bỏ qua dấu phẩy và không làm bất kỳ sự chia tách nào.

Câu hỏi ở đây là, có cách nào để nhanh chóng với regex này không? Tôi đã thực sự nhìn vào điều này solution nhưng điều này không có vẻ như nó bao gồm các ví dụ tôi đã đưa ra.

Một chức năng tổng quát hơn sẽ là một cái gì đó như thế này:

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

Một số sử dụng sẽ là như thế này:

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

có regex có thể xử lý này hoặc là nó cần thiết để tạo ra một chuyên ngành phân tích cú pháp?

+0

biểu thức thông thường sẽ không giúp bạn trong trường hợp này kể từ khi ngôn ngữ (tức là nhóm dây) bạn đang cố gắng phân tích cú pháp không phải là thường xuyên. Cho rằng bạn cho phép tùy ý lồng thẻ, không có cách nào dễ dàng để regex theo cách của bạn ra khỏi này. –

+1

Regex không thể thực tế xử lý việc này và bạn sẽ không muốn nó. Độ phức tạp là tuyến tính ở mức tối thiểu, vì vậy bạn nhất thiết phải luôn có được hiệu suất tốt hơn với bộ kiểm tra chẵn lẻ. Bạn không phải tự mình xây dựng nó. Mô-đun 'csv' của Python thực hiện rất nhiều tác vụ. –

+2

Argh, đừng nói rằng regex không thể xử lý nó! Có lẽ hương vị trăn không thể, nhưng những hương vị khác như PCRE có thể làm được! Đây là [một bằng chứng] (http://regex101.com/r/wU7lC9), chúng ta thậm chí có thể ưa thích và sử dụng các mẫu đệ quy để xem xét lồng nhau '<>()' – HamZa

Trả lời

8

Trong khi nó không phải là có thể sử dụng một Regular Expression, mã đơn giản sau đây sẽ đạt được kết quả mong muốn:

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 

Chạy này trong thông dịch viên:

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

'nếu char trong đóng: level - = 1 tiếp tục nếu char mở:' Điều đó sẽ cho phép bạn thêm dấu phân tách vừa mở và đóng, giống như trích dẫn chữ. do đó, thông điệp '" msg, với dấu phẩy "'. Không cần xử lý seprate cho trường hợp này. – kalhartt

4

Nếu bạn có biểu thức lồng nhau đệ quy , bạn có thể phân tách trên dấu phẩy và xác thực rằng chúng khớp với việc này với 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) 

Prints:

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

Các hành vi hiện nay là giống như '(something does not split), b, "in quotes", c'.split',') bao gồm giữ không gian hàng đầu và dấu ngoặc kép. Nó là tầm thường để loại bỏ các dấu ngoặc kép và không gian hàng đầu từ các lĩnh vực.

Thay đổi else dưới try tới:

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

Nhược điểm với cách tiếp cận này là sau đó bạn phải xây dựng các điều kiện để tái khâu các mục không được cho là chia nhỏ. – brandonscript

+1

Điều này không chính xác vì nó chia chuỗi '" obj <1, 2, 3> "'. – jmlopez

+0

+1 để trỏ đến thư viện thay vì tự mình lăn –

5

sử dụng vòng lặp và máy phát điện:

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"'] 
Các vấn đề liên quan