REGEX - соответствие любому символу, который повторяется n раз

Как совместить любой символ, повторяющий n раз?

Пример:

for input: abcdbcdcdd
for n=1:   ..........
for n=2:    .........
for n=3:     .. .....
for n=4:      .  . ..
for n=5:   no matches

Через несколько часов это лучшее выражение

(\w)(?=(?:.*\1){n-1,}) //where n is variable

который использует lookahead. Однако проблема с этим выражением такова:

for input: abcdbcdcdd
for n=1    .......... 
for n=2     ... .. .
for n=3      ..  .
for n=4       .
for n=5    no matches

Как вы можете видеть, когда lookahead соответствует символу, давайте посмотрим for n=4 line, d утверждение lookahead и сначала d, соответствующее регулярному выражению. Но оставшиеся d не совпадают, потому что впереди их еще нет d.

Надеюсь, я четко сформулировал эту проблему. Надеясь на ваши решения, спасибо заранее.

Ответы

Ответ 1

ищите n = 4 строки, d lookahead assertion выполнено и сначала d соответствует регулярному выражению. Но оставшиеся d не сопоставляются, потому что у них нет еще 3 перед ними.

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

Как и при любой реализации регулярного выражения, ответ зависит от аромата регулярного выражения. Вы можете создать решение с regex engine, потому что он позволяет изменять ширину.

Кроме того, я представлю более обобщенное решение ниже для perl-совместимых/подобных ароматов regex.


.net Решение

Как @PetSerAl указал в своем ответе, с переменной шириной lookbehinds, мы можем вернуться к началу строки и проверить, есть ли n.
демонстрация ideone

модуль regex в Python
Вы можете реализовать это решение в , используя regex module от Мэтью Барнетта, который также позволяет искать переменные ширины.

>>> import regex
>>> regex.findall( r'(\w)(?<=(?=(?>.*?\1){2})\A.*)', 'abcdbcdcdd')
['b', 'c', 'd', 'b', 'c', 'd', 'c', 'd', 'd']
>>> regex.findall( r'(\w)(?<=(?=(?>.*?\1){3})\A.*)', 'abcdbcdcdd')
['c', 'd', 'c', 'd', 'c', 'd', 'd']
>>> regex.findall( r'(\w)(?<=(?=(?>.*?\1){4})\A.*)', 'abcdbcdcdd')
['d', 'd', 'd', 'd']
>>> regex.findall( r'(\w)(?<=(?=(?>.*?\1){5})\A.*)', 'abcdbcdcdd')
[]


Обобщенное решение

В или любой из "perl-like", нет решения, которое фактически возвращало бы соответствие для каждого повторяющегося символа, но мы могли бы создать один и только один захват для каждого символа.

Стратегия

Для любого заданного n логика включает в себя:

  • Ранние совпадения: Сопоставление и захват каждого символа, за которым следует, по крайней мере, n больше случаев.
  • Окончательные снимки:
    • Сопоставьте и зафиксируйте символ, за которым следуют в точности n-1, и
    • также фиксирует каждое из следующих случаев.

Пример

for n = 3
input = abcdbcdcdd

Символ c имеет значение M, только один раз (как окончательный), а также следующие 2 вхождения также C, найденные в одном и том же совпадении:

abcdbcdcdd
  M  C C

а символ d (ранний) M ашот один раз:

abcdbcdcdd
   M

и (наконец) M еще раз, C удаляет остальные:

abcdbcdcdd
      M CC

Regex

/(\w)                        # match 1 character
(?:
    (?=(?:.*?\1){≪N≫})     # [1] followed by other ≪N≫ occurrences
  |                          #   OR
    (?=                      # [2] followed by:
        (?:(?!\1).)*(\1)     #      2nd occurence <captured>
        (?:(?!\1).)*(\1)     #      3rd occurence <captured>
        ≪repeat previous≫  #      repeat subpattern (n-1) times
                             #     *exactly (n-1) times*
        (?!.*?\1)            #     not followed by another occurence
    )
)/xg

Для n =

  1. /(\w)(?:(?=(?:.*?\1){2})|(?=(?:(?!\1).)*(\1)(?!.*?\1)))/g
    демонстрация
  2. /(\w)(?:(?=(?:.*?\1){3})|(?=(?:(?!\1).)*(\1)(?:(?!\1).)*(\1)(?!.*?\1)))/g
    демонстрация
  3. /(\w)(?:(?=(?:.*?\1){4})|(?=(?:(?!\1).)*(\1)(?:(?!\1).)*(\1)(?:(?!\1).)*(\1)(?!.*?\1)))/g
    демонстрация
  4. ... и т.д.

Псевдокод для генерации шаблона

// Variables: N (int)

character = "(\w)"
early_match = "(?=(?:.*?\1){" + N + "})"

final_match = "(?="
for i = 1; i < N; i++
    final_match += "(?:(?!\1).)*(\1)"
final_match += "(?!.*?\1))"

pattern = character + "(?:" + early_match + "|" + final_match + ")"

Код JavaScript

Я покажу реализацию с использованием , потому что мы можем проверить результат здесь (и если он работает в javascript, он работает в любом perl-совместимом регулярном выражении, включая , , , , и все языки, на которых реализовано , среди прочих).

var str = 'abcdbcdcdd';
var pattern, re, match, N, i;
var output = "";

// We'll show the results for N = 2, 3 and 4
for (N = 2; N <= 4; N++) {
    // Generate pattern
    pattern = "(\\w)(?:(?=(?:.*?\\1){" + N + "})|(?=";
    for (i = 1; i < N; i++) {
        pattern += "(?:(?!\\1).)*(\\1)";
    }
    pattern += "(?!.*?\\1)))";
    re = new RegExp(pattern, "g");
    
    output += "<h3>N = " + N + "</h3><pre>Pattern: " + pattern + "\nText: " + str;
    
    // Loop all matches
    while ((match = re.exec(str)) !== null) {
        output += "\nPos: " + match.index + "\tMatch:";
        // Loop all captures
        x = 1;
        while (match[x] != null) {
            output += " " + match[x];
            x++;
        }
    }
    
    output += "</pre>";
}

document.write(output);

Ответ 2

Регулярные выражения (и конечные автоматы) не могут рассчитывать на произвольные целые числа. Они могут рассчитывать только на предопределенное целое число и, к счастью, это ваш случай.

Решение этой задачи намного проще, если мы сначала построим недетерминированные конечные автоматы (NFA) ad, а затем преобразуем их в регулярное выражение.

Итак, следующие автоматы для n = 2 и входной алфавит = {a, b, c, d} введите описание изображения здесь

будет соответствовать любой строке, которая имеет ровно 2 повторения любого char. Если ни один символ не имеет 2 повторений (все символы появляются меньше или больше, чем два раза), строка не будет соответствовать.

Преобразование его в регулярное выражение должно выглядеть как

"^([^a]*a[^a]*a[^a]*)|([^b]*b[^b]*b[^b]*)|([^b]*c[^c]*c[^C]*)|([^d]*d[^d]*d[^d]*)$"

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

Ответ 3

С регулярными выражениями .NET вы можете сделать следующее:

(\w)(?<=(?=(?:.*\1){n})^.*) where n is variable

Где:

  • (\w) - любой символ, записанный в первой группе.
  • (?<=^.*) - утверждение lookbehind, которое возвращает нас к началу строки.
  • (?=(?:.*\1){n}) - выражение lookahead, чтобы увидеть, имеет ли строка n экземпляры этого символа.

Демо

Ответ 4

Я бы не использовал для этого регулярные выражения. Я бы использовал скриптовый язык, такой как python. Попробуйте эту функцию python:

alpha = 'abcdefghijklmnopqrstuvwxyz'
def get_matched_chars(n, s):
    s = s.lower()
    return [char for char in alpha if s.count(char) == n]

Функция вернет список символов, все из которых будут отображаться в строке s ровно n раз. Имейте в виду, что я только включил буквы в свой алфавит. Вы можете изменить альфа, чтобы представлять все, что вы хотите согласовать.