Как разделить строку запятыми, расположенными вне скобок? - PullRequest
22 голосов
/ 30 октября 2009

Я получил строку такого формата:

"Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"

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

Моя цель - разбить эту строку на список пар - (actor name, actor role).

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

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

import re
x = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
s = re.split(r'[()]', x) 
# ['Wilbur Smith ', 'Billy, son of John', ', Eddie Murphy ', 'John', ', Elvis Presley, Jane Doe ', 'Jane Doe', '']

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

Существуют ли более простые / приятные способы сделать это, либо с помощью одного регулярного выражения, либо с помощью небольшого фрагмента кода?

Ответы [ 10 ]

19 голосов
/ 30 октября 2009

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

>>> s = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
>>> r = re.compile(r'(?:[^,(]|\([^)]*\))+')
>>> r.findall(s)
['Wilbur Smith (Billy, son of John)', ' Eddie Murphy (John)', ' Elvis Presley', ' Jane Doe (Jane Doe)']

Выражение выше соответствует одному или нескольким:

  • не запятые, не открытые паренские символы
  • строки, которые начинаются с открытого имени, содержат 0 или более неотключенных символов, а затем - закрытое число

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

Также обратите внимание, что регулярные выражения не подходят для случаев, когда возможно вложение. Так, например, это будет неверно разделено:

"Wilbur Smith (son of John (Johnny, son of James), aka Billy), Eddie Murphy (John)"

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

5 голосов
/ 30 октября 2009
s = re.split(r',\s*(?=[^)]*(?:\(|$))', x) 

Заголовок сопоставляет все до следующей открытой скобки или до конца строки, если между ними нет закрывающей скобки. Это гарантирует, что запятая не находится внутри набора скобок.

5 голосов
/ 30 октября 2009

Я думаю, что лучший способ достичь этого - использовать встроенный в Python модуль csv .

Поскольку модуль csv только допускает один символ quotechar, вам потребуется заменить данные на входах, чтобы преобразовать () во что-то вроде | или ". Затем убедитесь, что вы используете подходящий диалект и поехали.

2 голосов
/ 30 октября 2009

Попытка чтения человеком регулярного выражения:

import re

regex = re.compile(r"""
    # name starts and ends on word boundary
    # no '(' or commas in the name
    (?P<name>\b[^(,]+\b)
    \s*
    # everything inside parentheses is a role
    (?:\(
      (?P<role>[^)]+)
    \))? # role is optional
    """, re.VERBOSE)

s = ("Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley,"
     "Jane Doe (Jane Doe)")
print re.findall(regex, s)

Выход:

[('Wilbur Smith', 'Billy, son of John'), ('Eddie Murphy', 'John'), 
 ('Elvis Presley', ''), ('Jane Doe', 'Jane Doe')]
1 голос
/ 27 ноября 2012

Этот пост мне очень помог. Я хотел разделить строку запятыми, расположенными вне кавычек. Я использовал это как стартер. Моя последняя строка кода была regEx = re.compile(r'(?:[^,"]|"[^"]*")+') Это добилось цели. Спасибо за тонну.

1 голос
/ 31 октября 2009

Вот общая техника, которую я использовал в прошлом для таких случаев:

Используйте функцию sub модуля re с функцией в качестве аргумента замены. Функция отслеживает открывающие и закрывающие скобки, скобки и фигурные скобки, а также одинарные и двойные кавычки и выполняет замену только за пределами таких подстрок в скобках и в кавычках. Затем вы можете заменить запятые без скобок / кавычки другим символом, который, как вы уверены, не появляется в строке (я использую разделитель групп ASCII / Unicode: код chr (29)), а затем сделать простую строку. разделить на этого персонажа. Вот код:

import re
def srchrepl(srch, repl, string):
    """Replace non-bracketed/quoted occurrences of srch with repl in string"""

    resrchrepl = re.compile(r"""(?P<lbrkt>[([{])|(?P<quote>['"])|(?P<sep>["""
                            + srch + """])|(?P<rbrkt>[)\]}])""")
    return resrchrepl.sub(_subfact(repl), string)

def _subfact(repl):
    """Replacement function factory for regex sub method in srchrepl."""
    level = 0
    qtflags = 0
    def subf(mo):
        nonlocal level, qtflags
        sepfound = mo.group('sep')
        if  sepfound:
            if level == 0 and qtflags == 0:
                return repl
            else:
                return mo.group(0)
        elif mo.group('lbrkt'):
            level += 1
            return mo.group(0)
        elif mo.group('quote') == "'":
            qtflags ^= 1            # toggle bit 1
            return "'"
        elif mo.group('quote') == '"':
            qtflags ^= 2            # toggle bit 2
            return '"'
        elif mo.group('rbrkt'):
            level -= 1
            return mo.group(0)
    return subf

Если у вас нет nonlocal в вашей версии Python, просто измените его на global и определите level и qtflags на уровне модуля.

Вот как это используется:

>>> GRPSEP = chr(29)
>>> string = "Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
>>> lst = srchrepl(',', GRPSEP, string).split(GRPSEP)
>>> lst
['Wilbur Smith (Billy, son of John)', ' Eddie Murphy (John)', ' Elvis Presley', ' Jane Doe (Jane Doe)']
1 голос
/ 30 октября 2009

Мой ответ не будет использовать регулярное выражение.

Я думаю, что должен работать простой сканер символов с состоянием "in_actor_name". Помните, что состояние "in_actor_name" завершается либо символом ")", либо запятой в этом состоянии.

Моя попытка:

s = 'Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)'

in_actor_name = 1
role = ''
name = ''
for c in s:
    if c == ')' or (c == ',' and in_actor_name):
        in_actor_name = 1
        name = name.strip()
        if name:
            print "%s: %s" % (name, role)
        name = ''
        role = ''
    elif c == '(':
        in_actor_name = 0
    else:
        if in_actor_name:
            name += c
        else:
            role += c
if name:
    print "%s: %s" % (name, role)

Выход:

Wilbur Smith: Billy, son of John
Eddie Murphy: John
Elvis Presley: 
Jane Doe: Jane Doe
0 голосов
/ 31 октября 2009

Ни один из приведенных выше ответов не является правильным, если в ваших данных есть какие-либо ошибки или помехи.

Легко найти хорошее решение, если вы знаете, что данные правильны каждый раз. Но что произойдет, если будут ошибки форматирования? Что бы ты хотел, чтобы произошло?

Предположим, есть круглые скобки? Предположим, есть непревзойденные скобки? Предположим, что строка заканчивается или начинается с запятой или имеет два подряд?

Все вышеперечисленные решения будут производить больше или меньше мусора и не сообщать вам об этом.

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

0 голосов
/ 30 октября 2009

разделить на ")"

>>> s="Wilbur Smith (Billy, son of John), Eddie Murphy (John), Elvis Presley, Jane Doe (Jane Doe)"
>>> s.split(")")
['Wilbur Smith (Billy, son of John', ', Eddie Murphy (John', ', Elvis Presley, Jane Doe (Jane Doe', '']
>>> for i in s.split(")"):
...   print i.split("(")
...
['Wilbur Smith ', 'Billy, son of John']
[', Eddie Murphy ', 'John']
[', Elvis Presley, Jane Doe ', 'Jane Doe']
['']

Вы можете выполнить дополнительную проверку, чтобы получить те имена, которые не поставляются с ().

0 голосов
/ 30 октября 2009

Я, конечно, согласен с @Wogan выше, что использование CSV moudle - это хороший подход.Сказав это, если вы все еще хотите попробовать решение регулярных выражений, попробуйте это, но вам придется адаптировать его к диалекту Python

string.split(/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/)

HTH

...