CodingBat repeatEnd с использованием regex

Я пытаюсь понять регулярное выражение как можно больше, поэтому я придумал это решение на основе regex для codingbat.com repeatEnd:

С учетом строки и int N верните строку из N повторений последних N символов строки. Вы можете предположить, что N находится между 0 и длиной строки, включительно.

public String repeatEnd(String str, int N) {
  return str.replaceAll(
    ".(?!.{N})(?=.*(?<=(.{N})))|."
      .replace("N", Integer.toString(N)),
    "$1"
  );
}

Объяснение по его частям:

  • .(?!.{N}): утверждает, что совпадающий символ является одним из последних N символов, убедившись, что за ним не осталось N символов.
  • (?=.*(?<=(.{N}))): в этом случае используйте lookforward, чтобы сначала пройти весь путь до конца строки, затем вложенный lookbehind, чтобы захватить последние N символов в \1. Обратите внимание, что это утверждение всегда будет истинным.

  • |.: если первое утверждение не выполнено (т.е. впереди по крайней мере N символов), то в любом случае совпадение с символом; \1 будет пустым.

  • В любом случае символ всегда сопоставляется; замените его на \1.

Мои вопросы:

  • Является ли эта методика вложенными утверждениями действительными? (т.е. оглядываясь назад во время просмотра?)
  • Есть ли более простое решение на основе регулярных выражений?

Бонусный вопрос

Do repeatBegin (как аналогично определено).

Я честно испытываю проблемы с этим!

Ответ 1

Хороший! Я не вижу возможности значительно улучшить это регулярное выражение, хотя я бы реорганизовал его, чтобы избежать ненужного использования отрицательной логики:

".(?=.{N})|.(?=.*(?<=(.{N})))"

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

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


EDIT: Я просто понял, что могу немного упростить регулярное выражение, поместив чередование в lookahead:

".(?=.{N}|.*(?<=(.{N})))"

Кстати, рассмотрели ли вы использование format() для создания регулярного выражения вместо replace()?

return str.replaceAll(
  String.format(".(?=.{%1$d}|.*(?<=(.{%1$d})))", N),
  "$1"
);

Ответ 2

Ого, что-то страшное reugx voodoo там!:)

  • Является ли эта методика вложенными утверждениями действительными? (т.е. оглядываясь назад во время просмотра?)

Да, это прекрасно действует в большинстве реализаций PCRE, о которых я знаю.

  • Есть ли более простое решение на основе регулярных выражений?

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

Ответ 3

Существует ли более простое решение на основе регулярного выражения?

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

"(?=.{0,N}$(?<=(.{N}))).|." // repeatEnd
          -or-
".(?<=^(?=(.{N})).{0,N})|." // repeatBegin

Как и ответ Алана Мура, это устраняет отрицательное утверждение, но даже не заменяет его положительным, поэтому теперь он имеет только 2 утверждения вместо 3.

Мне также нравится тот факт, что случай "else" является просто простым .. Я предпочитаю накладывать основную часть своего регулярного выражения на "рабочую" сторону чередования и держать "нерабочую" сторону максимально простой (обычно простой . или .*).