Перемещение индекса в соответствие регулярному выражению JavaScript
У меня есть это регулярное выражение для извлечения двойных слов из текста
/[A-Za-z]+\s[A-Za-z]+/g
И этот образец текста
Mary had a little lamb
Мой вывод - это
[0] - Mary had; [1] - a little;
В то время как мой ожидаемый результат:
[0] - Mary had; [1] - had a; [2] - a little; [3] - little lamb
Как я могу достичь этого результата? Насколько я понимаю, индекс поиска перемещается в конец первого совпадения. Как перенести его на одно слово?
Ответы
Ответ 1
Нарушение функции String.replace
Я использую небольшой трюк, используя функцию replace
. Поскольку функция replace
проходит через совпадения и позволяет нам указать функцию, возможность бесконечна. Результат будет в output
.
var output = [];
var str = "Mary had a little lamb";
str.replace(/[A-Za-z]+(?=(\s[A-Za-z]+))/g, function ($0, $1) {
output.push($0 + $1);
return $0; // Actually we don't care. You don't even need to return
});
Поскольку вывод содержит перекрывающуюся часть во входной строке, необходимо не потреблять следующее слово, когда мы сопоставляем текущее слово, используя look-ahead 1.
Регулярное выражение /[A-Za-z]+(?=(\s[A-Za-z]+))/g
выполняет точно так же, как я сказал выше: он будет потреблять только одно слово за раз с частью [A-Za-z]+
(начало регулярного выражения) и смотреть вперед для следующего слова (?=(\s[A-Za-z]+))
2 а также захватить согласованный текст.
Функция, переданная функции replace
, получит согласованную строку в качестве первого аргумента и захваченного текста в последующих аргументах. (Есть больше - проверьте документацию - мне они здесь не нужны). Так как прогноз вперед - это нулевая ширина (вход не потребляется), все совпадение также является удобным первым словом. Текст захвата в look-ahead войдет во второй аргумент.
Правильное решение с RegExp.exec
Обратите внимание, что функция String.replace
несет накладные расходы на замену, так как результат замены не используется вообще. Если это неприемлемо, вы можете переписать вышеуказанный код с помощью функции RegExp.exec
в цикле:
var output = [];
var str = "Mary had a little lamb";
var re = /[A-Za-z]+(?=(\s[A-Za-z]+))/g;
var arr;
while ((arr = re.exec(str)) != null) {
output.push(arr[0] + arr[1]);
}
Сноска
-
В другом аромате регулярного выражения, которое поддерживает отрицательный внешний вид переменной ширины, можно получить предыдущее слово, но JavaScript regex не поддерживает отрицательный внешний вид!.
-
(?=pattern)
является синтаксисом для поиска вперед.
Приложение
String.match
не может использоваться здесь, поскольку он игнорирует группу захвата, когда используется флаг g
. Группа захвата необходима в регулярном выражении, так как нам нужно искать, чтобы избежать использования ввода и совпадения с перекрывающимся текстом.
Ответ 2
Это можно сделать без регулярного выражения
"Mary had a little lamb".split(" ")
.map(function(item, idx, arr) {
if(idx < arr.length - 1){
return item + " " + arr[idx + 1];
}
}).filter(function(item) {return item;})
Ответ 3
Здесь нерепрессивное решение (это не совсем регулярная проблема).
function pairs(str) {
var parts = str.split(" "), out = [];
for (var i=0; i < parts.length - 1; i++)
out.push([parts[i], parts[i+1]].join(' '));
return out;
}
Передайте свою строку, и вы получите массив назад.
Примечание: если вы беспокоитесь о не-словах на вашем входе (создавая случай для регулярных выражений!), вы можете запускать тесты на parts[i]
и parts[i+1]
внутри цикла for
. Если тесты не выполняются: не нажимайте их на out
.
Ответ 4
Возможно, вам понравится способ:
var s = "Mary had a little lamb";
// Break on each word and loop
s.match(/\w+/g).map(function(w) {
// Get the word, a space and another word
return s.match(new RegExp(w + '\\s\\w+'));
// At this point, there is one "null" value (the last word), so filter it out
}).filter(Boolean)
// There, we have an array of matches -- we want the matched value, i.e. the first element
.map(Array.prototype.shift.call.bind(Array.prototype.shift));
Если вы запустите это в своей консоли, вы увидите ["Mary had", "had a", "a little", "little lamb"]
.
Таким образом, вы сохраняете свое исходное регулярное выражение и можете делать другие вещи, которые вы хотите в нем. Хотя с некоторым кодом вокруг него, чтобы он действительно работал.
Кстати, этот код не является кросс-браузером. Следующие функции не поддерживаются в IE8 и ниже:
- Array.prototype.filter
- Array.prototype.map
- Function.prototype.bind
Но они легко сглаживаются. Или же такая же функциональность легко достижима с помощью for
.
Ответ 5
Здесь мы идем:
Вы все еще не знаете, как работает внутренний указатель регулярного выражения, поэтому я объясню вам небольшой пример:
Mary had a little lamb
с этим регулярным выражением /[A-Za-z]+\s[A-Za-z]+/g
Здесь первая часть регулярного выражения: [A-Za-z]+
будет соответствовать Mary
, поэтому указатель будет в конце y
Mary had a little lamb
^
В следующей части (\s[A-Za-z]+
) он будет соответствовать пробелу, за которым следует другое слово, поэтому...
Mary had a little lamb
^
Указатель будет содержать слово had
. Итак, вот ваша проблема, вы увеличиваете внутренний указатель регулярного выражения, не желая, как это решить? Lookaround - ваш друг. С lookarounds (lookahead и lookbehind) вы можете пройти через свой текст, не увеличивая основной внутренний указатель регулярного выражения (для этого он использовал бы другой указатель).
Итак, в конце регулярное выражение, которое будет соответствовать вам, будет: ([A-Za-z]+(?=\s[A-Za-z]+))
Пояснение:
Единственное, что вы не знаете об этом регулярном выражении, это часть (?=\s[A-Za-z]+)
, это означает, что за [A-Za-z]+
должно следовать слово, иначе регулярное выражение не будет соответствовать. И это именно то, что вам кажется нужным, потому что промежуточный указатель не будет увеличен и будет соответствовать каждому слову, кроме последнего, потому что последнее не будет сопровождаться словом.
Затем, как только у вас есть, вам нужно только заменить то, что вы сделали прямо сейчас.
Здесь у вас есть рабочий пример DEMO
Ответ 6
В полном восхищении понятием "взгляд вперед" я все же предлагаю функцию pairwise
(demo), поскольку она действительно Задача Regex - токенизировать поток символов, а решение о том, что делать с токенами, зависит от бизнес-логики. По крайней мере, это мое мнение.
Позор, что Javascript еще не попал, но это могло бы сделать это:
function pairwise(a, f) {
for (var i = 0; i < a.length - 1; i++) {
f(a[i], a[i + 1]);
}
}
var str = "Mary had a little lamb";
pairwise(str.match(/\w+/g), function(a, b) {
document.write("<br>"+a+" "+b);
});