Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Разделение строк с разделителями-запятыми в python

Автор вопроса: jmlopezОтветов: 3

Этот вопрос уже задавался и на него уже много раз давался ответ. Некоторые примеры: [1], [2]. Но, похоже, нет чего-то более общего. То, что я ищу, - это способ разделить строки запятыми, которые не находятся в кавычках или парах разделителей. Например:

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

Вопрос в том, есть ли способ быстро сделать это с помощью регулярных выражений? Я действительно изучал это решение, но похоже, что оно не охватывает приведенные мной примеры.

Более общая функция будет выглядеть примерно так:

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"', ',', [('<', '>'), ('(', ')'), ('"', '"')]

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

  • Регулярные выражения в этом случае вам не помогут, так как язык (т.е. группа строк), который вы пытаетесь разобрать, не является регулярным. Учитывая, что вы допускаете произвольное вложение тегов, нет простого способа регулярного выражения выйти из этого. Yuval Adam 16.12.2013
  • На самом деле Regex не может справиться с этим, и вы бы этого не хотели. Сложность, как минимум, линейна, поэтому вы обязательно всегда получите лучшую производительность с проверкой четности. При этом вам не нужно строить его самостоятельно. Модуль Python csv выполняет большую часть работы. Slater Victoroff 16.12.2013
  • Argh, не говорите, что регулярное выражение не может справиться с этим! Возможно, вариант Python не мог, но другие варианты, такие как PCRE, могли бы это сделать! Это доказательство, мы могли бы даже придумать и использовать рекурсивные шаблоны для учета вложенных <>() HamZa 16.12.2013
  • Это также возможно с регулярным выражением, если вы знаете максимальную глубину вложенной рекурсии элементов в квадратных скобках. Но в Python, где у вас нет поддержки рекурсивных регулярных выражений, используйте более удобный парсер функция. Dean Taylor 16.12.2013
  • Аааа и я это сделал, теперь вопрос, почему я сделал О_о ? HamZa 16.12.2013

pythonstringregexsplit

Ответы

Ответ 1 · рейтинг 8 · Aaron Cronin

Хотя использовать регулярное выражение невозможно, следующий простой код позволит достичь желаемого результата:

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

Ответ 2 · рейтинг 5 · behzad.nouri

используя итераторы и генераторы:

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 · рейтинг 4 · dawg

Если у вас есть рекурсивные вложенные выражения, вы можете разделить их запятыми и проверить, что они совпадают, сделав это с помощью 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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы