Оптимизация регулярных выражений веселое время упражнение для детей! Принимая регулярное выражение Гнарфа в качестве отправной точки:
^(.)\1*(.)?(?:\1*\2*)*(.)?(?:\1*\2*\3*)*$
Я заметил, что здесь были вложенные и последовательные * s, которые могут вызвать большой возврат. Например, в «abcaaax» он будет пытаться сопоставить последнюю строку из «a» как единое целое \ 1 * длины 3, a \ 1 * длины два, за которым следует один \ 1, a \ 1 и 2-длина \ 1 * или три одинарных \ 1. Эта проблема усугубляется, когда у вас более длинные строки, особенно когда из-за регулярного выражения ничто не мешает \ 1 быть тем же символом, что и \ 2.
^(.)\1*(.)?(?:\1|\2)*(.)?(?:\1|\2|\3)*$
Это было в два раза быстрее, чем оригинал, тестирование на Python PCRE matcher. (Это быстрее, чем настраивать его в PHP, извините.)
Это по-прежнему имеет проблему в том, что (.)?
не может ничего сопоставить, а затем продолжить остальную часть матча. \1|\2
все равно будет совпадать с \ 1, даже если нет совпадения с \ 2, что приведет к потенциальному откату назад, пытаясь ввести предложения \1|\2
и \1|\2|\3
ранее, когда они не могут привести к совпадению. Эту проблему можно решить, переместив необязательность ?
по всем конечным пунктам:
^(.)\1*(?:(.)(?:\1|\2)*(?:(.)(?:\1|\2|\3)*)?)?$
Это снова было вдвое быстрее.
Все еще существует потенциальная проблема, заключающаяся в том, что любой из \ 1, \ 2 и \ 3 может быть одним и тем же символом, что может привести к большему откату назад, когда выражение не совпадает. Это остановит его, если использовать отрицательный взгляд, чтобы не соответствовать предыдущему символу:
^(.)\1*(?:(?!\1)(.)(?:\1|\2)*(?:(?!\1|\2)(.)(?:\1|\2|\3)*)?)?$
Однако в Python с моими случайными тестовыми данными я не заметил значительного ускорения от этого. Ваш пробег может варьироваться в PHP в зависимости от тестовых данных, но он может быть уже достаточно хорошим. Обязательное соответствие (* +) могло бы помочь, если бы оно было доступно здесь.
Ни одно регулярное выражение не работает лучше, чем более простая для чтения альтернатива Python:
len(set(s))<=3
Аналогичный метод в PHP, вероятно, будет с count_chars :
strlen(count_chars($s, 3))<=3
Я не проверял скорость, но я очень ожидал бы, что она будет быстрее, чем регулярное выражение, в дополнение к тому, что она намного лучше читается.
Так что, по сути, я просто потратил впустую свое время, играя с регулярными выражениями. Не тратьте свое время, сначала найдите простые строковые методы, прежде чем прибегать к регулярным выражениям!