Как сопоставить перекрывающиеся строки с регулярным выражением?
Допустим, у меня есть строка
"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 - количество букв в последовательности)