Как сопоставить перекрывающиеся строки с регулярным выражением?

Допустим, у меня есть строка

"12345"

Если я .match(/\d{3}/g)Я только один матч, "123", Почему я не получаю [ "123", "234", "345" ]?

6 ответов

Решение

Вы не можете сделать это с помощью одного регулярного выражения, но вы можете подойти довольно близко:

var pat = /(?=(\d{3}))\d/g;
var results = [];
var match;

while ( (match = pat.exec( '1234567' ) ) != null ) { 
  results.push( match[1] );
}

console.log(results);

Другими словами, вы фиксируете все три цифры в предвкушении, затем возвращаетесь назад и сопоставляете один символ обычным способом, чтобы просто продвинуться по позиции совпадения. Неважно, как вы потребляете этого персонажа; . работает так же хорошо \d, И если вы действительно чувствуете себя авантюрным, вы можете использовать только предвкушение и позволить JavaScript справиться с ситуацией.

Этот код адаптирован из этого ответа. Я бы пометил этот вопрос как его дубликат, но ФП приняла другой, более слабый ответ.

string#match с глобальным флагом регулярное выражение возвращает массив совпавших подстрок. /\d{3}/g регулярное выражение соответствует и потребляет (= считывает в буфер и переводит его индекс в позицию сразу после текущего сопоставленного символа) 3-значная последовательность. Таким образом, после "поедания" 123, индекс расположен после 3 и единственная подстрока, оставшаяся для анализа 45 - здесь нет совпадений.

Я думаю, что техника, используемая на https://regex101.com/, также стоит рассмотреть здесь: используйте утверждение нулевой ширины (положительный прогноз с группой захвата), чтобы проверить все позиции внутри входной строки. После каждого теста RegExp.lastIndex (это целочисленное свойство чтения / записи регулярных выражений, которое указывает индекс, с которого начинается следующее совпадение), добавляется "вручную", чтобы избежать бесконечного цикла.

Обратите внимание, что этот метод реализован в.NET (Regex.Matches), Python (re.findall), PHP (preg_match_all).

Вот демо:

var re = /(?=(\d{3}))/g; 
var str = '12345';
var res = [];
var m;
 
while ((m = re.exec(str)) !== null) {
    if (m.index === re.lastIndex) {
        re.lastIndex++;
    }
    res.push(m[1]);
}

document.body.innerHTML = JSON.stringify(res);

Вот демонстрация regex101.com

Когда выражение совпадает, оно обычно использует совпадающие символы. Итак, после того, как выражение подобрано 123, только 45 слева, что не соответствует шаблону.

Чтобы ответить на вопрос "Как", вы можете вручную изменить индекс последнего совпадения (требуется цикл):

var input = '12345', 
    re = /\d{3}/g, 
    r = [], 
    m;
while (m = re.exec(input)) {
    re.lastIndex -= m[0].length - 1;
    r.push(m[0]);
}
r; // ["123", "234", "345"]

Вот функция для удобства:

function matchOverlap(input, re) {
    var r = [], m;
    // prevent infinite loops
    if (!re.global) re = new RegExp(
        re.source, (re+'').split('/').pop() + 'g'
    );
    while (m = re.exec(input)) {
        re.lastIndex -= m[0].length - 1;
        r.push(m[0]);
    }
    return r;
}

Примеры использования:

matchOverlap('12345', /\D{3}/)      // []
matchOverlap('12345', /\d{3}/)      // ["123", "234", "345"]
matchOverlap('12345', /\d{3}/g)     // ["123", "234", "345"]
matchOverlap('1234 5678', /\d{3}/)  // ["123", "234", "567", "678"]
matchOverlap('LOLOL', /lol/)        // []
matchOverlap('LOLOL', /lol/i)       // ["LOL", "LOL"]

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

let s = "12345"
let m = Array.from(s.slice(2), (_, i) => s.slice(i, i+3))
console.log(m)

Использование (?=(\w{3}))

(3 - количество букв в последовательности)

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