Регулярное выражение и количество вхождений

#regex #repeat

#регулярное выражение #повторять

Вопрос:

Я хочу, чтобы оно соответствовало строке из 2-15 символов, содержащей только заглавные / строчные буквы и цифры; дефисы разрешены только между ними.

 /^[a-z0-9][a-z0-9-]{0,13}[a-z0-9]$/i
  

Например: a-b-c ab a-b a-bcdef-g-h-i

Как мне убедиться, что никакие два дефиса не отображаются подряд?

Ответ №1:

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

 /^(?!.*--)[a-z0-9][a-z0-9-]{0,13}[a-z0-9]$/i
  

(?!.*--) гарантирует невозможность сопоставления -- в любом месте строки (без фактического использования каких-либо символов в сопоставлении).

Кроме того, нет необходимости экранировать тире, если это первый или последний символ в классе символов.

Если вы не заинтересованы в прогнозах с неопределенными кванторами (например, Donal Fellows), другим способом было бы

 /^[a-z0-9](?:[a-z0-9]|-(?!-)){0,13}[a-z0-9]$/i
  

(?:[a-z0-9]|-(?!-)){0,13} соответствует либо буквенно-цифровому символу, либо тире, если за ним не следует другое тире, повторяется до 13 раз.

Что касается производительности (проверено в Python 3.2.2):

 >>> import timeit
>>> timeit.timeit(stmt='r.match("a--bcdefghijklmop-qrstuvwxyz")', 
... setup='import re; r=re.compile(r"^(?!.*--)[a-z0-9][a-z0-9-]{0,13}[a-z0-9]$")')
0.699529247317531
>>> timeit.timeit(stmt='r.match("a--bcdefghijklmop-qrstuvwxyz")', 
... setup='import re; r=re.compile(r"^[a-z0-9](?:[a-z0-9]|-(?!-)){0,13}[a-z0-9]$")')
0.6518945164968741
>>> timeit.timeit(stmt='r.match("a-bcdefghijklmop-qrstuvwxy--z")', 
... setup='import re; r=re.compile(r"^(?!.*--)[a-z0-9][a-z0-9-]{0,13}[a-z0-9]$")')
0.5857406334929749
>>> timeit.timeit(stmt='r.match("a-bcdefghijklmop-qrstuvwxy--z")', 
... setup='import re; r=re.compile(r"^[a-z0-9](?:[a-z0-9]|-(?!-)){0,13}[a-z0-9]$")')
2.2273210211646415
  

Таким образом, (?!.*--) в худшем случае это происходит немного медленнее ( -- в начале строки, поэтому много возвратов), но в лучшем случае это в четыре раза быстрее ( -- в конце строки, поэтому возврата почти нет).

Комментарии:

1. Я не очень заинтересован в использовании отрицательных прогнозных утверждений с * квантификатором внутри.

2. @DonalFellows: Могу я (возможно, наивно) спросить, почему?

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

4. @DonalFellows: Звучит разумно. В этом случае, не * можно было бы опустить и все еще иметь желаемый эффект?

5. @ean5533: В этом случае вы не можете опустить . * потому что «—» может встречаться в любом месте строки длиной 15.