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

StackOverflow https://stackoverflow.com/questions/1648537

  •  22-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

Один из способов сделать это - использовать 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)']

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

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

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

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

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

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

Другие советы

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

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

s = re.split(r',\s*(?=[^)]*(?:\(|$))', x) 

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

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

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

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

Я думаю, что простой сканер символов с состоянием "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

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

Используйте функцию 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

Если в вашей версии 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)']

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

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

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

HTH

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top