2013-12-15 4 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"', ',', [('<', '>'), ('(', ')'), ('"', '"')] 

REGEX бы быть в состоянии справиться с этим или необходимо создать специализированный анализатор?

+0

Регулярные выражения не помогут вам в этом случае, так как язык (т. е. группа строк), которую вы пытаетесь разобрать, не является регулярной. Учитывая, что вы допускаете произвольное вложенность тегов, нет простого способа повторного выбора из этого правила. –

+1

Regex не может справиться с этим, и вы не захотите этого. Сложность линейна как минимум, поэтому вы всегда должны получать лучшую производительность с помощью проверки на четность. Вам не нужно строить его самостоятельно. Модуль 'csv' Python выполняет большую часть работы. –

+2

Argh, не говорите, что регулярное выражение не может справиться с этим! Возможно, вкус питона не мог, но другие вкусы, такие как 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

'if char in closes: level - = 1 continue, если char открывается:' Это должно позволить вам добавить разделители, которые открываются и закрываются, как буквальная цитата. поэтому '' 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',') в том числе сохраняя ведущие пробелы и кавычки. Тривиально убирать кавычки и ведущие пробелы из полей.

Изменение else под try к:

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"'] 
Смежные вопросы