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

В процессе работы над этим ответом я наткнулся на аномалию с повторяющимися регулярными выражениями Python.

Скажем, мне дали строку CSV с произвольным количеством элементов в кавычках и без кавычек:

21, 2, '23.5R25 ETADT ',' описание, запятая '

Я хочу заменить все ',' вне цитаты с '\t', Так что я хотел бы вывод:

21 \ t2 \ t'23.5R25 ETADT '\ t'описание, с запятой'

Поскольку в строке будет несколько совпадений, естественно, я буду использовать g модификатор регулярного выражения. Регулярное выражение, которое я буду использовать, будет соответствовать символам вне кавычек или строки в кавычках, за которой следует ',':

('[^']*'|[^',]*),\s*

И я заменю на:

\1\t

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

21 \ t2 \ t'23.5R25 ETADT '\ t'описание \ с запятой'

Вы можете увидеть живой пример такого поведения здесь: https://regex101.com/r/sG9hT3/2

В. Есть ли способ закрепить g Изменено ли регулярное выражение для начала сопоставления с персонажем после предыдущего совпадения?


Для тех, кто знаком с могущественными регулярными выражениями Perl, Perl предоставляет \G, Что позволяет нам получить конец последней совпавшей позиции. Так что в Perl я могу выполнить то, что я прошу, с помощью регулярного выражения:

\G('[^']*'|[^',]*),\s*

Это приведет к несоответствию в последнем цитируемом элементе. Потому что вместо того, чтобы позволить реализации регулярного выражения найти точку, где регулярное выражение соответствует \G заставит его начинать сопоставление с первого символа:

"Описание, с запятой"

1 ответ

Решение

Вы можете использовать следующее регулярное выражение с re.search:

,?\s*([^',]*(?:'[^']*'[^',]*)*)

Посмотреть демо-версию регулярного выражения (я изменяю его на ,?[ ]*([^',\n]*(?:'[^'\n]*'[^',\n]*)*) так как это многострочная демка)

Здесь регулярное выражение совпадает (в регулярном значении слова)...

  • ,? - 1 или 0 запятая
  • \s* - 0 или больше пробелов
  • ([^',]*(?:'[^']*'[^',]*)*) - Группа 1, хранящая захваченный текст, который состоит из...
    • [^',]* - 0 или более символов, кроме , а также '
    • (?:'[^']*'[^',]*)* - 0 или более последовательностей...
      • '[^']*' - а 'string'подстрока, не содержащая апострофов
      • [^',]* - 0 или более символов, кроме , а также ',

Если вы хотите использовать re.match и сохранять захваченные тексты внутри групп захвата, это невозможно, поскольку механизм регулярных выражений Python не сохраняет все записи в стеке, как это делает механизм регулярных выражений.NET CaptureCollection,

Кроме того, регулярное выражение Python не поддерживает \G оператор, поэтому вы не можете привязать какой-либо подшаблон в конце успешного совпадения здесь.

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

import re

def successive_matches(pattern,text,pos=0):
  ptrn = re.compile(pattern)
  match = ptrn.match(text,pos)
  while match:
    yield match.group()
    if match.end() == pos:
      break
    pos = match.end()
    match = ptrn.match(text,pos)
  if pos < len(text) - 1:
    yield text[pos:]

for matched_text in successive_matches(r"('[^']*'|[^',]*),\s*","21, 2, '23.5R25 ETADT', 'description, with a comma'"):
    print matched_text

Смотрите демоверсию IDEONE, вывод

21, 
2, 
'23.5R25 ETADT', 
'description, with a comma'
Другие вопросы по тегам