Давайте попробуем понять регулярное выражение, построив его. Во-первых, палиндром должен начинаться и заканчиваться одинаковой последовательностью символов в противоположном направлении:
^(.)(.)(.) ... \3\2\1$
мы хотим переписать это так, чтобы за ...
следовала только конечная длина паттернов, чтобы мы могли преобразовать ее в *
. Это возможно с предвидением:
^(.)(?=.*\1$)
(.)(?=.*\2\1$)
(.)(?=.*\3\2\1$) ...
но есть еще необычные части. Что если мы сможем «записать» ранее захваченные группы? Если это возможно, мы могли бы переписать это как:
^(.)(?=.*(?<record>\1\k<record>)$) # \1 = \1 + (empty)
(.)(?=.*(?<record>\2\k<record>)$) # \2\1 = \2 + \1
(.)(?=.*(?<record>\3\k<record>)$) # \3\2\1 = \3 + \2\1
...
, который может быть преобразован в
^(?:
(.)(?=.*(\1\2)$)
)*
Почти хорошо, за исключением того, что \2
(записанный снимок) изначально не пуст. Это просто не сможет ничего соответствовать. Нам нужно, чтобы он совпадал с пустым, если записанный снимок не существует. Вот как закрадывается условное выражение.
(?(2)\2|) # matches \2 if it exist, empty otherwise.
так что наше выражение становится
^(?:
(.)(?=.*(\1(?(2)\2|))$)
)*
Теперь он соответствует первой половине палиндрома. Как насчет второй половины? Что ж, после сопоставления первой половины записанный снимок \2
будет содержать вторую половину. Итак, давайте просто положим это в конце.
^(?:
(.)(?=.*(\1(?(2)\2|))$)
)*\2$
Мы хотим позаботиться и о палиндроме нечетной длины. Между первой и второй половиной будет свободный персонаж.
^(?:
(.)(?=.*(\1(?(2)\2|))$)
)*.?\2$
Это хорошо работает за исключением в одном случае - когда есть только 1 символ. Это снова из-за \2
совпадений ничего. Итак
^(?:
(.)(?=.*(\1(?(2)\2|))$)
)*.?\2?$
# ^ since \2 must be at the end in the look-ahead anyway.