Подтвердить что ты не робот

Свернуть и захватить повторяющийся шаблон в выражении с одним выражением

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

Итак, скажем, текст:

старт: тест тест-Lorem Ipsum-, сэр doloret-и т.д.-и т.д.-что-то: конец

В этом примере есть 8 элементов внутри, но говорят, что оно может иметь от 3 до 10 элементов.

Мне идеально понравилось бы что-то вроде этого:
start:(?:(\w+)-?){3,10}:end приятный и чистый, но он только фиксирует последнее совпадение. см. здесь

Обычно я использую что-то подобное в простых ситуациях:

start:(\w+)-(\w+)-(\w+)-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?:end

3 группы обязательны, а еще 7 необязательны из-за предела 10, но это не выглядит "приятным", и было бы больно писать и отслеживать, если максимальный предел составлял 100, а совпадения были более сложными. демо

И самое лучшее, что я мог сделать до сих пор:

start:(\w+)-((?1))-((?1))-?((?1))?-?((?1))?-?((?1))?-?((?1))?-?((?1))?:end

короче, особенно если матчи сложны, но все еще долго. демо

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

Мне больше всего интересно, как это можно сделать в PCRE, но другие вкусы тоже будут в порядке.

Обновление:

Цель состоит в том, чтобы проверить соответствие и зафиксировать отдельные токены внутри match 0 только с помощью RegEx без ограничения OS/Software/Programming-Language

Обновление 2 (щедрость):

С помощью @nhahtdh help я добрался до RegExp ниже, используя \G:

(?:start:(?=(?:[\w]+(?:-|(?=:end))){3,10}:end)|(?!^)\G-)([\w]+)

demo еще короче, но может быть описана без повторения кода

Меня также интересует аромат ECMA, и поскольку он не поддерживает \G, задается вопросом, есть ли другой способ, особенно без использования модификатора /g.

4b9b3361

Ответ 1

Прочитайте это первым!

Этот пост должен показать возможность, а не одобрить подход "все регулярное выражение" к проблеме. Автор написал 3-4 варианта, каждый из которых имеет тонкую ошибку, которые сложно обнаружить, до достижения текущего решения.

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

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

Резюме

  • .NET поддерживает захват повторяющегося шаблона с помощью CaptureCollection class.
  • Для языков, поддерживающих \G и look-behind, мы можем создать регулярное выражение, которое работает с глобальной функцией сопоставления. Нелегко написать это полностью правильно и легко написать тонкое багги-регулярное выражение.
  • Для языков без \G и поддержки позади: можно эмулировать \G с помощью ^ путем прерывания строки ввода после одного совпадения. (В этом ответе не указано).

Решение

Это решение предполагает, что механизм регулярных выражений поддерживает \G совпадение границ, прогноз (?=pattern) и look-behind (?<=pattern). Java, Perl, PCRE,.NET, Ruby regex поддерживает все перечисленные выше функции.

Однако вы можете пойти с вашим регулярным выражением в .NET. Поскольку .NET поддерживает захват всех экземпляров, которые сопоставляются группой захвата, которая повторяется с помощью класса CaptureCollection.

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

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)

DEMO. Конструкция \w+- повторена, затем \w+:end.

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)

DEMO. Конструкция \w+ для первого элемента, затем -\w+ повторяется. (Спасибо ka ᵠ за предложение). Эта конструкция проще рассуждать о ее правильности, поскольку есть меньше изменений.

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

Описание

Разложим регулярное выражение:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?<=-)\G
)
(\w+)
(?:-|:end)

Самая легкая часть для распознавания - это (\w+) в строке до последней, которая является словом, которое вы хотите захватить.

Последняя строка также довольно легко распознается: за словом, которое должно быть сопоставлено, может следовать - или :end.

Я разрешаю регулярному выражению свободно начинать сопоставление в любом месте строки. Другими словами, start:...:end может появляться в любом месте строки и любое количество раз; регулярное выражение будет просто соответствовать всем словам. Вам нужно обработать только возвращаемый массив, чтобы отделить его от соответствующих токенов.

Что касается объяснения, начало регулярного выражения проверяет наличие строки start:, а следующие проверки проверяют, что количество слов находится в пределах заданного предела и заканчивается на :end. Либо это, либо мы проверяем, что символ перед предыдущим совпадением равен -, и продолжить с предыдущего соответствия.

Для другой конструкции:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?!^)\G-
)
(\w+)

Все почти то же самое, за исключением того, что мы сначала сопоставляем start:\w+ перед сопоставлением повторения формы -\w+. В отличие от первой конструкции, где мы сначала сопоставляем start:\w+- и повторяющиеся экземпляры \w+- (или \w+:end для последнего повторения).

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

  • Нам нужно проверить количество слов между start: и :end (как часть требования исходного регулярного выражения).

  • \G также соответствует началу строки! (?!^) необходим для предотвращения такого поведения. Не заботясь об этом, регулярное выражение может вызвать совпадение, если нет start:.

    Для первой конструкции look-behind (?<=-) уже предотвращает этот случай ((?!^) подразумевается (?<=-)).

  • Для первой конструкции (?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end) нам нужно убедиться, что после :end мы ничего не смешаем. Внешний вид предназначен для этой цели: он предотвращает любой мусор после :end от соответствия.

    Вторая конструкция не сталкивается с этой проблемой, так как мы будем застревать в : (of :end) после того, как мы сравним все токены между ними.

Версия проверки

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

(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)

(Look-behind также не требуется, но нам все еще нужно (?!^), чтобы предотвратить \G от совпадения начала строки).

Строительство

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

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

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

  • Напишите регулярное выражение, которое покрывает часть перед повторением (например, start:). Назовем это префиксом regex.
  • Совпадение и захват первого экземпляра. (например, (\w+))
    (В этот момент первый экземпляр и разделитель должны быть сопоставлены)
  • Добавьте \G как чередование. Обычно также необходимо предотвратить совпадение начала строки.
  • Добавьте разделитель (если есть). (например, -)
    (После этого шага остальные маркеры должны быть также сопоставлены, за исключением последнего, возможно)
  • Добавьте часть, которая покрывает часть после повторения (если необходимо) (например, :end). Назовем эту часть после регулярного выражения суффикса повторения (добавим ли мы его в конструкцию неважно).
  • Теперь трудная часть. Вы должны проверить, что:
    • Нет другого способа начать матч, кроме префикса regex. Обратите внимание на ветвь \G.
    • Невозможно начать любое совпадение после того, как будет найдено соответствие регулярного выражения суффикса. Обратите внимание, как ветка \G начинает совпадение.
    • Для первой конструкции, если вы смешаете регулярное выражение суффикса (например, :end) с разделителем (например, -) в чередовании, убедитесь, что вы не разрешаете суффиксное выражение как разделитель.

Ответ 2

Хотя теоретически возможно написать одно выражение, гораздо более практично сначала согласовать внешние границы, а затем выполнить разбиение на внутреннюю часть.

В ECMAScript я бы написал это следующим образом:

'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end'
    .match(/^start:([\w-]+):end$/)[1] // match the inner part
    .split('-') // split inner part (this could be a split regex as well)

В PHP:

$txt = 'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end';
if (preg_match('/^start:([\w-]+):end$/', $txt, $matches)) {
    print_r(explode('-', $matches[1]));
}

Ответ 3

Конечно, вы можете использовать регулярное выражение в этой цитируемой строке.

"(?<a>\\w+)-(?<b>\\w+)-(?:(?<c>\\w+)" \
"(?:-(?<d>\\w+)(?:-(?<e>\\w+)(?:-(?<f>\\w+)" \
"(?:-(?<g>\\w+)(?:-(?<h>\\w+)(?:-(?<i>\\w+)" \
"(?:-(?<j>\\w+))?" \
")?)?)?" \
")?)?)?" \
")"

Это хорошая идея? Нет, я так не думаю.

Ответ 4

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

http://regex101.com/r/gK0lX1

Вам придется проверять количество групп самостоятельно. Без глобального флага вы получаете только одно совпадение, а не все совпадения - измените {3,10} на {1,5}, и вместо этого вы получите результат 'sir'.

import re

s = "start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end"
print re.findall(r"(\b\w+?\b)(?:-|:end)", s)

производит

['test', 'test', 'lorem', 'ipsum', 'sir', 'doloret', 'etc', 'etc', 'something']

Ответ 5

При объединении:

  • Ваше наблюдение: любое повторение одной группы захвата приведет к перезаписи последнего захвата, таким образом возвращая только последний захват группы захвата.
  • Знание: любой вид захвата, основанный на частях, а не на целом, делает невозможным установить ограничение на количество повторений, которое будет повторять двигатель регулярного выражения. Предел должен быть метаданных (не регулярных выражений).
  • С требованием, чтобы ответ не включал программирование (цикл), а также ответ, который включает просто скопирование вставки групп захвата, как вы это делали в своем вопросе.

Можно сделать вывод, что это невозможно.

Обновление: Есть несколько двигателей регулярных выражений, для которых p. 1 не обязательно верно. В этом случае регулярное выражение, указанное вами start:(?:(\w+)-?){3,10}:end, выполнит задание (источник).