Поведение match в JavaScript

Рейтинг: 3Ответов: 2Опубликовано: 22.09.2014

Всем привет. Снова немного о регулярных выражениях в JS. Я ожидаю, что код:

'aabb'.match(/a.*b/g);

, вернет мне массив:

['aab', 'aabb', 'ab', 'abb'];

Почему он возвращает массив только с одним элементом?

['aabb']

Разве str.match(RegExp); не должен возвращать массив всех совпадений по регулярному выражению?

Ответы

▲ 11

Надо объединить возможности регулярных выражений, такие как жадный и ленивый поиск, захват подстроки в блоке просмотра и глобальный модификатор:

re = /(?=(a.*?b))(?=(a.*b))/g;
s = 'aabb';
arr = [];
while ((m=re.exec(s)) !== null)  {
  if (m.index === re.lastIndex) {
    re.lastIndex++;
  }
  if (m[1] && arr.indexOf(m[1]) === -1)
    arr.push(m[1]);
  if (m[2] && arr.indexOf(m[2]) === -1)
    arr.push(m[2]);
  
}

document.write(JSON.stringify(arr));

В данном случае, выражение при нахождении совпадения вернет пустой результат в m[0], но благодаря подмаскам в m[1] и m[2] будут извлечены захваченные подстроки. Так как эти подстроки могут быть идентичны, необходима проверка существующих элементов.

ОТВЕТ НА КОММЕНТАРИЙ:

Для поиска всех пермутаций на основе любого шаблона, можно воспользоваться следующим кодом. Его особенность: собираем все совпадения, а потом последовательно определяем, есть ли совпадения внутри совпадения. Таким образом, нет необходимости проверять абсолютно все пермутации входной строки.

function permRegex(re_str, s) {
  var arr = [];                              // Все наши пермутации
  var caps = [];                             // Все начальные совпадения (caps)
  var re = RegExp(re_str, "g");              // Глобальное выражение для caps
  while ((m = re.exec(s)) !== null) {        // Ищем...
    caps.push(m[0]);                         // и добавляем совпадение в caps
  }
  var re2 = RegExp("^" + re_str + "$");      // Создаем неглобальное выражение для проверки на совпадение ЦЕЛОЙ строки
  for (var c=0; c<caps.length; c++)          // Перебираем наши совпадения
    for(var i=0; i < caps[c].length; i++)     // От первого символа...
      for(var j=i+1; j <= caps[c].length; j++) // До последнего, слева направо
        if(re2.test(str = caps[c].substring(i, j))) // Если совпадает с шаблоном
          arr.push(str);                       // Добавляем к пермутациям

  return arr;
}

document.getElementById("a1").innerHTML = JSON.stringify(permRegex("a.*b", "aabb")) + "<br/>";
// "aabb"  => ["aabb", "aab", "abb", "ab"]
document.getElementById("a1").innerHTML += JSON.stringify(permRegex("a.*b", "aabbb")) + "<br/>";
// "aabbb" => ["aabbb", "aabb", "aab", "abbb", "abb", "ab"]
document.getElementById("a1").innerHTML += JSON.stringify(permRegex("е.*т", "А где енот?"));
// "е.*т", "А где енот?" => ["е енот", "енот"]

function permRegex(re_str, s) {
  var arr = [];                              // Все наши пермутации
  var caps = [];                             // Все начальные совпадения (caps)
  var re = RegExp(re_str, "g");              // Глобальное выражение для caps
  while ((m = re.exec(s)) !== null) {        // Ищем...
    caps.push(m[0]);                         // и добавляем совпадение в caps
  }
  var re2 = RegExp("^" + re_str + "$");      // Создаем неглобальное выражение для проверки на совпадение ЦЕЛОЙ строки
  for (var c=0; c<caps.length; c++)          // Перебираем наши совпадения
    for(var i=0; i < caps[c].length; i++)     // От первого символа...
      for(var j=i+1; j <= caps[c].length; j++) // До последнего, слева направо
        if(re2.test(str = caps[c].substring(i, j))) // Если совпадает с шаблоном
          arr.push(str);                       // Добавляем к пермутациям
  
  return arr;
}

document.getElementById("a1").innerHTML = JSON.stringify(permRegex("a.*b", "aabb")) + "<br/>";
// "aabb"  => ["aabb", "aab", "abb", "ab"]
document.getElementById("a1").innerHTML += JSON.stringify(permRegex("a.*b", "aabbb")) + "<br/>";
// "aabbb" => ["aabbb", "aabb", "aab", "abbb", "abb", "ab"]
document.getElementById("a1").innerHTML += JSON.stringify(permRegex("е.*т", "А где енот?"));
// "е.*т", "А где енот?" => ["е енот", "енот"]

$( "#run" ).on( "click", function() {
  $( "#a1" ).html( JSON.stringify(permRegex( $( "#regex" ).val(), $( "#text" ).val() ) ) );
} );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="regex" placeholder="regex" /> <input id="text" placeholder="text" /><br/>
<button id="run">Ok</button>
<div id="a1"/>

▲ 3

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

function doit(r, s) {
  var res = [], cur;
  r = RegExp('^(?:' + r.source + ')$', r.toString().replace(/^[\s\S]*\/(\w*)$/, '$1'));
  r.global = false;
  for (var q = 0; q < s.length; ++q)
    for (var w = q; w <= s.length; ++w)
      if (r.test(cur = s.substring(q, w)))
        res.push(cur);
  return res;
}

$( "#run" ).on( "click", function() {
  $( "#a1").text( JSON.stringify( doit(
    eval( $( "#regex" ).val() ),
    $( "#text" ).val()
    ) ) );
} );

doit( /a.*b/g, 'aabb' )   // Array [ "aab", "aabb", "ab", "abb" ]
doit( /a(.|cc)/g, 'acc' ) // Array ["ac","acc"]
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="regex" placeholder="/a.*b/" />
<input id="text" placeholder="aaabb" />
<br/>
<button id="run">Ok</button>
<div id="a1" />