codingBat repeatEnd используя регулярное выражение

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

Если задана строка и 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,

Мои вопросы:

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

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

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

У меня правда есть проблемы с этим!

3 ответа

Решение

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

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

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

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


РЕДАКТИРОВАТЬ: я только что понял, что могу немного упростить регулярное выражение, поместив чередование внутри прогнозирования:

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

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

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

Оу, это какое-то страшное регулярное вуду!:)

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам