Перемещение индекса в соответствие регулярному выражению 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);
});

​