Скобочные группы

Часть шаблона может быть заключена в скобки (...). Такие выделенные части шаблона называют «скобочными выражениями» или «скобочными группами».

У такого выделения есть два эффекта:

  1. Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через String#match или RegExp#exec.
  2. Если поставить квантификатор после скобки, то он применится ко всей скобке, а не всего лишь к одному символу.

Пример

В примере ниже, шаблон (go)+ находит один или более повторяющихся 'go':

alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo"

Без скобок, шаблон /go+/ означал бы g, после которого идёт одна или более o, например: goooo. А скобки «группируют» (go) вместе.

Содержимое группы

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

Например, найти HTML-тег можно шаблоном <.*?>.

После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое <...> в скобки: <(.*?)>. Тогда оно будет доступно отдельно.

При поиске методом String#match в результирующем массиве будет сначала всё совпадение, а далее – скобочные группы. В шаблоне <(.*?)> скобочная группа только одна:

var str = '<h1>Привет, мир!</h1>';
var reg = /<(.*?)>/;

alert( str.match(reg) ); // массив: <h1>, h1

Заметим, что метод String#match выдаёт скобочные группы только при поиске без флага /.../g. В примере выше он нашёл только первое совпадение <h1>, а закрывающий </h1> не нашёл, поскольку без флага /.../g ищется только первое совпадение.

Для того, чтобы искать и с флагом /.../g и со скобочными группами, используется метод RegExp#exec:

var str = '<h1>Привет, мир!</h1>';
var reg = /<(.*?)>/g;

var match;

while ((match = reg.exec(str)) !== null) {
  // сначала выведет первое совпадение: <h1>,h1
  // затем выведет второе совпадение: </h1>,/h1
  alert(match);
}

Теперь найдено оба совпадения <(.*?)>, каждое – массив из полного совпадения и скобочных групп (одна в данном случае).

Вложенные группы

Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.

Например, при поиске тега в <span class="my"> нас может интересовать:

  1. Содержимое тега целиком: span class="my".
  2. В отдельную переменную для удобства хотелось бы поместить тег: span.
  3. Также может быть удобно отдельно выделить атрибуты class="my".

Добавим скобки в регулярное выражение:

var str = '<span class="my">';

var reg = /<(([a-z]+)\s*([^>]*))>/;

alert( str.match(reg) ); // <span class="my">, span class="my", span, class="my"

Вот так выглядят скобочные группы:

На нулевом месте – всегда совпадение полностью, далее – группы. Нумерация всегда идёт слева направо, по открывающей скобке.

В данном случае получилось, что группа 1 включает в себя содержимое групп 2 и 3. Это совершенно нормальная ситуация, которая возникает, когда нужно выделить что-то отдельное внутри большей группы.

Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен undefined).

Например, рассмотрим регэксп a(z)?(c)?. Он ищет "a", за которой не обязательно идёт буква "z", за которой необязательно идёт буква "c".

Если напустить его на строку из одной буквы "a", то результат будет таков:

var match = 'a'.match(/a(z)?(c)?/)

alert( match.length ); // 3
alert( match[0] ); // a
alert( match[1] ); // undefined
alert( match[2] ); // undefined

Массив получился длины 3, но все скобочные группы – undefined.

А теперь более сложная ситуация, строка ack:

var match = 'ack'.match(/a(z)?(c)?/)

alert( match.length ); // 3
alert( match[0] ); // ac, всё совпадение
alert( match[1] ); // undefined, для (z)? ничего нет
alert( match[2] ); // c

Длина массива результатов по-прежнему 3. Она постоянна. А вот для скобочной группы (z)? в ней ничего нет, поэтому результат: ["ac", undefined, "c"].

Исключение из запоминания через ?:

Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать их содержимое в массиве не нужно.

Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало ?:.

Например, мы хотим найти (go)+, но содержимое скобок (go) в отдельный элемент массива выделять не хотим.

Для этого нужно сразу после открывающей скобки поставить ?:, то есть: (?:go)+.

Например:

var str = "Gogo John!";
var reg = /(?:go)+ (\w+)/i;

var result = str.match(reg);

alert( result.length ); // 2
alert( result[1] ); // John

В примере выше массив результатов имеет длину 2 и содержит только полное совпадение и результат (\w+). Это удобно в тех случаях, когда содержимое скобок нас не интересует.

Задачи

Регулярное выражение для поиска 3-значного цвета вида #abc: /#[a-f0-9]{3}/i.

Нужно добавить ещё три символа, причём нужны именно три, четыре или семь символов не нужны. Эти три символа либо есть, либо нет.

Самый простой способ добавить – просто дописать в конец регэкспа: /#[a-f0-9]{3}([a-f0-9]{3})?/i

Можно поступить и хитрее: /#([a-f0-9]{3}){1,2}/i.

Здесь регэксп [a-f0-9]{3} заключён в скобки, чтобы квантификатор {1,2} применялся целиком ко всей этой структуре.

В действии:

var re = /#([a-f0-9]{3}){1,2}/gi;

var str = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( str.match(re) ); // #3f3 #AA0ef #abc

В последнем выражении #abcd было найдено совпадение #abc. Чтобы этого не происходило, добавим в конец \b:

var re = /#([a-f0-9]{3}){1,2}\b/gi;

var str = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( str.match(re) ); // #3f3 #AA0ef

Напишите регулярное выражение, которое находит цвет в формате #abc или #abcdef. То есть, символ #, после которого идут 3 или 6 шестнадцатиричных символа.

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

var re = /* ваш регэксп */

var str = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( str.match(re) ); // #3f3 #AA0ef

P.S. Значения из любого другого количества букв, кроме 3 и 6, такие как #abcd, не должны подходить под регэксп.

Регулярное выражение для числа, возможно, дробного и отрицательного: -?\d+(\.\d+)?. Мы уже разбирали его в предыдущих задачах.

Оператор – это [-+*/]. Заметим, что дефис - идёт в списке первым, так как на любой позиции, кроме первой и последней, он имеет специальный смысл внутри [...], и его понадобилось бы экранировать.

Кроме того, когда мы оформим это в JavaScript-синтаксис /.../ – понадобится заэкранировать слэш /.

Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними.

Полное регулярное выражение будет таким: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?.

Чтобы получить результат в виде массива, добавим скобки вокруг тех данных, которые нам интересны, то есть – вокруг чисел и оператора: (-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?).

Посмотрим в действии:

var re = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;

alert( "1.2 + 12".match(re) );

Итоговый массив будет включать в себя компоненты:

  • result[0] == "1.2 + 12" (вначале всегда полное совпадение)
  • result[1] == "1" (первая скобка)
  • result[2] == "2" (вторая скобка – дробная часть (\.\d+)?)
  • result[3] == "+" (…)
  • result[4] == "12" (…)
  • result[5] == undefined (последняя скобка, но у второго числа дробная часть отсутствует)

Нам из этого массива нужны только числа и оператор. А, скажем, дробная часть сама по себе – не нужна.

Уберём её из запоминания, добавив в начало скобки ?:, то есть: (?:\.\d+)?.

Итого, решение:

function parse(expr) {
  var re = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;

  var result = expr.match(re);

  if (!result) return;
  result.shift();

  return result;
}

alert( parse("-1.23 * 3.45") );  // -1.23, *, 3.45

Арифметическое выражение состоит из двух чисел и операции между ними, например:

  • 1 + 2
  • 1.2 * 3.4
  • -3 / -6
  • -2 - 2

Список операций: "+", "-", "*" и "/".

Также могут присутствовать пробелы вокруг оператора и чисел.

Напишите функцию, которая будет получать выражение и возвращать массив из трёх аргументов:

  1. Первое число.
  2. Оператор.
  3. Второе число.
Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.