разбить строку разделителем контекстно-зависимым способом - PullRequest
2 голосов
/ 19 июня 2009

Например, я хочу разделить

str = '"a,b,c",d,e,f'

в

["a,b,c",'d','e','f']

(т.е. не разбивать цитируемую часть). В этом случае это можно сделать с помощью

re.findall('".*?"|[^,]+',str)

Однако, если

str = '"a,,b,c",d,,f'

Я хочу

["a,,b,c",'d','','f']

т.е. Я хочу поведение, похожее на функцию разбиения Python. Есть ли способ, которым я могу сделать это в одну (маленькую) строку, возможно, используя библиотеку re Python?

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

Ответы [ 7 ]

2 голосов
/ 20 июня 2009
re.split(',(?=(?:[^"]*"[^"]*")*[^"]*$)', str)

После сопоставления запятой, если впереди есть нечетное количество кавычек, запятая должна быть внутри пары кавычек, поэтому она не считается разделителем. Очевидно, что это не учитывает возможность избежать кавычек, но это может быть обработано в случае необходимости - это просто делает регулярное выражение примерно в два раза менее уродливым, чем уже есть. : D

2 голосов
/ 20 июня 2009

Используйте модуль csv, так как он настоящий парсер. Регулярные выражения неоптимальны (или совершенно не подходят) для большинства вещей, связанных с соответствующими разделителями, в которых изменяются правила (я не уверен, является ли эта конкретная грамматика регулярной или нет). Возможно, вам удастся создать регулярное выражение, которое будет работать в этом случае, но это будет довольно сложно (особенно в таких случаях, как «Он сказал,« Как дела »).

1 голос
/ 20 июня 2009

Страница 271 из Friedl's Мастеринг регулярных выражений имеет регулярное выражение для извлечения возможно заключенных в кавычки полей CSV, но требует немного постобработки:

>>> re.findall('(?:^|,)(?:"((?:[^"]|"")*)"|([^",]*))',str)
[('a,b,c', ''), ('', 'd'), ('', 'e'), ('', 'f')]
>>> re.findall('(?:^|,)(?:"((?:[^"]|"")*)"|([^",]*))','"a,b,c",d,,f')
[('a,b,c', ''), ('', 'd'), ('', ''), ('', 'f')]

Тот же шаблон с подробным флагом:

csv = re.compile(r"""
    (?:^|,)
    (?: # now match either a double-quoted field
        # (inside, paired double quotes are allowed)...
        " # (double-quoted field's opening quote)
          (    (?: [^"] | "" )*    )
        " # (double-quoted field's closing quote)
    |
      # ...or some non-quote/non-comma text...
        ( [^",]* )
    )""", re.X)
1 голос
/ 20 июня 2009

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

0 голосов
/ 20 июня 2009

Вот очень короткая функция, которая будет делать то же самое:

def split (aString):
    splitByQuotes = (",%s,"%aString).split('"')
    splitByQuotes[0::2] = [x.split(",")[1:-1] for x in splitByQuotes[0::2]]
    return [a.strip() \
        for b in splitByQuotes \
        for a in (b if type(b)==list else [b])]

Он разбивает строку, в которой находятся кавычки, создавая список, в котором каждый четный элемент - это материал вне кавычек, а каждый нечетный элемент - это материал, который был заключен в кавычки. Материал в кавычках он оставляет в покое, материал вне его разделяется там, где запятые. Теперь у нас есть список чередующихся списков и строк, которые мы затем разворачиваем с последней строки. Причиной оборачивания строки в запятые в начале и удаления запятых в середине является предотвращение появления лишних пустых элементов в списке. Должна быть возможность обрабатывать пробелы - в конце я добавил функцию strip (), чтобы она вырабатывала чистый вывод, но это не обязательно.

использование:

>>> print split('c, , "a,,b,c",d,"moo","f"')
['c', '', 'a,,b,c', 'd', 'moo', 'f']
0 голосов
/ 20 июня 2009

Вот функция, выполняющая задачу:

def smart_split(data, delimiter=","):
    """ Performs splitting with string preservation. This reads both single and
        double quoted strings.
    """
    result = []
    quote_type = None
    buffer = ""
    position = 0
    while position < len(data):
        if data[position] in ["\"", "'"]:
            quote_type = data[position]
            while quote_type is not None:
                position += 1
                if data[position] == quote_type:
                    quote_type = None
                    position += 1
                else:
                    buffer += data[position]
        if data[position] == delimiter:
            result.append(buffer)
            buffer = ""
        else:
            buffer += data[position]
        position += 1
    result.append(buffer)
    return result

Пример использования:

str = '"a,b,c",d,e,f'
print smart_split(str)
# Prints: ['a,b,c', 'd', 'e', 'f']
0 голосов
/ 20 июня 2009

Вы можете приблизиться, используя не жадные спецификаторы. Самое близкое, что у меня есть:

>>> re.findall('(".*?"|.*?)(?:,|$)',  '"a,b,c",d,e,f')
['"a,,b,c"', 'd', '', 'f', '']

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

>>> re.findall('(".*?"|.*?)(?:,|$)', '"a,b,c",d,e,f,')
['"a,,b,c"', 'd', '', 'f', '']

так что вам нужно будет выполнить некоторые ручные настройки в конце - что-то вроде:

matches = regex,findall(s)
if not s.endswith(","): matches.pop()

или

matches = regex.findall(s+",")[:-1]

Возможно, есть лучший способ.

...