Существует ли версия JavaScript String.indexOf (), которая допускает регулярные выражения?

214

В javascript есть ли эквивалент String.indexOf (), который принимает регулярное выражение вместо строки для первого первого параметра, но при этом разрешает второй параметр?

Мне нужно сделать что-то вроде

str.indexOf(/[abc]/ , i);

и

str.lastIndexOf(/[abc]/ , i);

Хотя String.search () принимает в качестве параметра регулярное выражение, он не позволяет мне указать второй аргумент!

Изменить:
это оказалось сложнее, чем я думал, поэтому я написал небольшую тестовую функцию для проверки всех предоставленных решений ... предполагается, что regexIndexOf и regexLastIndexOf были добавлены в объект String.

function test (str) {
    var i = str.length +2;
    while (i--) {
        if (str.indexOf('a',i) != str.regexIndexOf(/a/,i)) 
            alert (['failed regexIndexOf ' , str,i , str.indexOf('a',i) , str.regexIndexOf(/a/,i)]) ;
        if (str.lastIndexOf('a',i) != str.regexLastIndexOf(/a/,i) ) 
            alert (['failed regexLastIndexOf ' , str,i,str.lastIndexOf('a',i) , str.regexLastIndexOf(/a/,i)]) ;
    }
}

и я проверяю, как следует, чтобы убедиться, что по крайней мере для одного символа регулярное выражение, результат такой же, как если бы мы использовали indexOf

// ищем a среди xes
test ('xxx');
тест ( 'ахх');
тест ( 'хах');
тест ( 'XXA');
тест ( 'ах');
тест ( 'Хаа');
тест ( 'а');
тест ( 'ааа');

похлопывание
источник
|внутри [ ]соответствует буквальному символу |. Вы, наверное, имели в виду [abc].
Маркус Жардерот
да, спасибо, вы правы, я исправлю это, но само регулярное выражение не имеет значения ...
Pat
Обновил мой ответ Пэт, спасибо за любые отзывы.
Джейсон Бантинг
Я нашел более простой и эффективный подход - просто использовать string.match (/ [AZ] /). Если нет много, то метод возвращает нуль, в противном случае вы получите объект, вы можете сделать матч (/ [AZ] /) индекс , чтобы получить индекс первой заглавной буквы.
Syler

Ответы:

129

Комбинируя некоторые из уже упомянутых подходов (indexOf, очевидно, довольно прост), я думаю, что эти функции помогут вам:

String.prototype.regexIndexOf = function(regex, startpos) {
    var indexOf = this.substring(startpos || 0).search(regex);
    return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
    regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : ""));
    if(typeof (startpos) == "undefined") {
        startpos = this.length;
    } else if(startpos < 0) {
        startpos = 0;
    }
    var stringToWorkWith = this.substring(0, startpos + 1);
    var lastIndexOf = -1;
    var nextStop = 0;
    while((result = regex.exec(stringToWorkWith)) != null) {
        lastIndexOf = result.index;
        regex.lastIndex = ++nextStop;
    }
    return lastIndexOf;
}

Очевидно, что изменение встроенного объекта String вызовет красные флаги для большинства людей, но это может быть один раз, когда это не так уж важно; просто знайте об этом.


ОБНОВЛЕНИЕ: отредактировано regexLastIndexOf()так, что это, кажется, lastIndexOf()теперь подражает . Пожалуйста, дайте мне знать, если это все еще не удается и при каких обстоятельствах.


ОБНОВЛЕНИЕ: Проходит все тесты, найденные в комментариях на этой странице, и мои собственные. Конечно, это не значит, что это пуленепробиваемый. Любые отзывы приветствуются.

Джейсон Бантинг
источник
Вы вернете regexLastIndexOfтолько индекс последнего непересекающегося матча.
Маркус Жардерот
Извините, не ОГРОМНЫЙ парень с регулярными выражениями - не могли бы вы привести пример, который мог бы заставить меня потерпеть неудачу? Я ценю возможность узнать больше, но ваш ответ не поможет кому-то столь же невежественному, как я. :)
Джейсон Бантинг
Джейсон Я только что добавил функцию для проверки в вопросе. это терпит неудачу (среди других тестов) следующее 'axx'.lastIndexOf (' a ', 2)! =' axx'.regexLastIndexOf (/ a /, 2)
Pat
2
Я думаю , что это более эффективно использовать regex.lastIndex = result.index + 1;вместо regex.lastIndex = ++nextStop;. Мы надеемся, что следующий матч будет проходить намного быстрее, без потери результата.
Гедрокс
1
Если вы предпочитаете извлекать его из npm, эти две утилиты теперь доступны на NPM: npmjs.com/package/index-of-regex
Capaj
185

Экземпляры Stringконструктора имеют .search()метод, который принимает RegExp и возвращает индекс первого совпадения.

Чтобы начать поиск с определенной позиции (имитируя второй параметр .indexOf()), вы можете sliceотключить первые iсимволы:

str.slice(i).search(/re/)

Но это приведет к получению индекса в более короткой строке (после обрезки первой части), поэтому вы захотите добавить длину отрубленной части ( i) к возвращаемому индексу, если это не так -1. Это даст вам индекс в исходной строке:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}
Гленн
источник
1
От вопроса: хотя String.search () принимает регулярное выражение в качестве параметра, он не позволяет мне указать второй аргумент!
Пат
14
str.substr (i) .search (/ re /)
Гленн
6
Отличное решение, но результат немного другой. indexOf вернет число с начала (независимо от смещения), тогда как это вернет позицию из смещения. Так что, для паритета, вы захотите что-то вроде этого:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger
39

У меня есть короткая версия для вас. Это хорошо работает для меня!

var match      = str.match(/[abc]/gi);
var firstIndex = str.indexOf(match[0]);
var lastIndex  = str.lastIndexOf(match[match.length-1]);

И если вы хотите прототип версии:

String.prototype.indexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.indexOf(match[0]) : -1;
}

String.prototype.lastIndexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.lastIndexOf(match[match.length-1]) : -1;
}

РЕДАКТИРОВАТЬ : если вы хотите добавить поддержку fromIndex

String.prototype.indexOfRegex = function(regex, fromIndex){
  var str = fromIndex ? this.substring(fromIndex) : this;
  var match = str.match(regex);
  return match ? str.indexOf(match[0]) + fromIndex : -1;
}

String.prototype.lastIndexOfRegex = function(regex, fromIndex){
  var str = fromIndex ? this.substring(0, fromIndex) : this;
  var match = str.match(regex);
  return match ? str.lastIndexOf(match[match.length-1]) : -1;
}

Чтобы использовать это, так просто, как это:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);
pmrotule
источник
Это на самом деле хороший трюк. Было бы замечательно, если бы вы расширили его, чтобы также взять startIndexпараметр как обычно indeoxOfи lastIndexOfсделать.
Роберт Коритник
@RobertKoritnik - я отредактировал свой ответ, чтобы поддержать startIndex(или fromIndex). Надеюсь, поможет!
pmrotule
lastIndexOfRegexТакже следует добавить обратно значение fromIndexк результату.
Питер
Ваш алгоритм будет разбит по следующему сценарию: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));результат будет 1, когда он должен быть 7, потому что indexOf будет искать в первый раз «romeo», независимо от того, находится он в начале слова или нет.
КорелК
13

Использование:

str.search(regex)

Смотрите документацию здесь.

rmg.n3t
источник
11
@ OZZIE: Нет, не совсем. Это в основном ответ Гленна (с ~ 150 ответами), за исключением того, что он не имеет никакого объяснения , не поддерживает стартовую позицию, кроме как 0, и был опубликован ... семь лет спустя.
ccjmne
7

На основании ответа BaileyP. Основное отличие состоит в том, что эти методы возвращаются, -1если шаблон не может быть сопоставлен.

Изменить: Благодаря ответу Джейсона Бантинга у меня появилась идея. Почему бы не изменить .lastIndexсвойство регулярного выражения? Хотя это будет работать только для шаблонов с глобальным флагом ( /g).

Изменить: Обновлено, чтобы пройти тест-кейсы.

String.prototype.regexIndexOf = function(re, startPos) {
    startPos = startPos || 0;

    if (!re.global) {
        var flags = "g" + (re.multiline?"m":"") + (re.ignoreCase?"i":"");
        re = new RegExp(re.source, flags);
    }

    re.lastIndex = startPos;
    var match = re.exec(this);

    if (match) return match.index;
    else return -1;
}

String.prototype.regexLastIndexOf = function(re, startPos) {
    startPos = startPos === undefined ? this.length : startPos;

    if (!re.global) {
        var flags = "g" + (re.multiline?"m":"") + (re.ignoreCase?"i":"");
        re = new RegExp(re.source, flags);
    }

    var lastSuccess = -1;
    for (var pos = 0; pos <= startPos; pos++) {
        re.lastIndex = pos;

        var match = re.exec(this);
        if (!match) break;

        pos = match.index;
        if (pos <= startPos) lastSuccess = pos;
    }

    return lastSuccess;
}
Маркус Жардеро
источник
Это кажется наиболее многообещающим на данный момент (после нескольких исправлений синтаксиса) :-) Сбой только нескольких тестов на граничных условиях. Такие вещи , как 'axx'.lastIndexOf (' а ', 0) =! Axx'.regexLastIndexOf (/ а /, 0) ... Я смотрю в нее , чтобы увидеть , если я могу исправить эти случаи
Pat
6

Вы можете использовать substr.

str.substr(i).match(/[abc]/);
Андру Лувизи
источник
Из известной книги JavaScript, опубликованной О'Рейли: «substr не был стандартизирован ECMAScript и поэтому устарел». Но мне нравится основная идея того, к чему ты клонишь.
Джейсон Бантинг
1
Это не проблема. Если вы ДЕЙСТВИТЕЛЬНО беспокоитесь об этом, используйте взамен String.substring () - вам просто нужно сделать математику немного иначе. Кроме того, JavaScript не должен быть на 100% обязательным по отношению к его родительскому языку.
Питер Бейли
Это не проблема - если ваш код работает с реализацией, которая не реализует substr, потому что они хотят придерживаться стандартов ECMAScript, у вас будут проблемы. Конечно, заменить его на подстроку не так сложно, но хорошо знать об этом.
Джейсон Бантинг
1
В тот момент, когда у вас возникают проблемы, у вас есть очень очень простые решения. Я думаю, что комментарии разумны, но отрицательное голосование было педантичным.
ВоронойПотато
Не могли бы вы отредактировать свой ответ, чтобы предоставить рабочий демонстрационный код?
vsync
5

RexExpэкземпляры уже имеют свойство lastIndex (если они глобальные), поэтому я копирую регулярное выражение, слегка его модифицируя в соответствии с нашими целями, execопределяя его в строке и просматривая lastIndex. Это неизбежно будет быстрее, чем зацикливание строки. (У вас достаточно примеров того, как поместить это в прототип строки, верно?)

function reIndexOf(reIn, str, startIndex) {
    var re = new RegExp(reIn.source, 'g' + (reIn.ignoreCase ? 'i' : '') + (reIn.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

function reLastIndexOf(reIn, str, startIndex) {
    var src = /\$$/.test(reIn.source) && !/\\\$$/.test(reIn.source) ? reIn.source : reIn.source + '(?![\\S\\s]*' + reIn.source + ')';
    var re = new RegExp(src, 'g' + (reIn.ignoreCase ? 'i' : '') + (reIn.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

reIndexOf(/[abc]/, "tommy can eat");  // Returns 6
reIndexOf(/[abc]/, "tommy can eat", 8);  // Returns 11
reLastIndexOf(/[abc]/, "tommy can eat"); // Returns 11

Вы также можете создать прототип функции для объекта RegExp:

RegExp.prototype.indexOf = function(str, startIndex) {
    var re = new RegExp(this.source, 'g' + (this.ignoreCase ? 'i' : '') + (this.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

RegExp.prototype.lastIndexOf = function(str, startIndex) {
    var src = /\$$/.test(this.source) && !/\\\$$/.test(this.source) ? this.source : this.source + '(?![\\S\\s]*' + this.source + ')';
    var re = new RegExp(src, 'g' + (this.ignoreCase ? 'i' : '') + (this.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};


/[abc]/.indexOf("tommy can eat");  // Returns 6
/[abc]/.indexOf("tommy can eat", 8);  // Returns 11
/[abc]/.lastIndexOf("tommy can eat"); // Returns 11

Краткое объяснение того, как я изменяю RegExp: indexOfя просто должен убедиться, что установлен глобальный флаг. Из-за lastIndexOfтого, что я использую отрицательный прогноз, чтобы найти последнее вхождение, если оно RegExpуже не совпадает в конце строки.

Prestaul
источник
4

Это не изначально, но вы, конечно, можете добавить эту функциональность

<script type="text/javascript">

String.prototype.regexIndexOf = function( pattern, startIndex )
{
    startIndex = startIndex || 0;
    var searchResult = this.substr( startIndex ).search( pattern );
    return ( -1 === searchResult ) ? -1 : searchResult + startIndex;
}

String.prototype.regexLastIndexOf = function( pattern, startIndex )
{
    startIndex = startIndex === undefined ? this.length : startIndex;
    var searchResult = this.substr( 0, startIndex ).reverse().regexIndexOf( pattern, 0 );
    return ( -1 === searchResult ) ? -1 : this.length - ++searchResult;
}

String.prototype.reverse = function()
{
    return this.split('').reverse().join('');
}

// Indexes 0123456789
var str = 'caabbccdda';

alert( [
        str.regexIndexOf( /[cd]/, 4 )
    ,   str.regexLastIndexOf( /[cd]/, 4 )
    ,   str.regexIndexOf( /[yz]/, 4 )
    ,   str.regexLastIndexOf( /[yz]/, 4 )
    ,   str.lastIndexOf( 'd', 4 )
    ,   str.regexLastIndexOf( /d/, 4 )
    ,   str.lastIndexOf( 'd' )
    ,   str.regexLastIndexOf( /d/ )
    ]
);

</script>

Я не полностью протестировал эти методы, но они, кажется, работают до сих пор.

Питер Бейли
источник
Обновлено для обработки этих дел
Питер Бейли
каждый раз, когда я собираюсь принять этот ответ, я нахожу новый случай! Это дает разные результаты! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Пат
ну, конечно, они - str.lastIndexOf будут выполнять приведение типов к шаблону - преобразовывая его в строку. Строка "/ [d] /", скорее всего, не найдена во входных данных, поэтому возвращаемое значение -1 действительно точно.
Питер Бейли
Понял. После прочтения спецификации на String.lastIndexOf () - я просто неправильно понял, как работает этот аргумент. Эта новая версия должна справиться с этим.
Питер Бейли
Что-то все еще не правильно, но уже поздно ... Я попытаюсь получить контрольный пример и, возможно, исправить это утром. Извините за беспокойство.
Пат
2

После того, как все предложенные решения так или иначе не прошли мои тесты, (отредактируйте: некоторые были обновлены, чтобы пройти тесты после того, как я написал это), я нашел реализацию mozilla для Array.indexOf и Array.lastIndexOf

Я использовал их для реализации моей версии String.prototype.regexIndexOf и String.prototype.regexLastIndexOf следующим образом:

String.prototype.regexIndexOf = function(elt /*, from*/)
  {
    var arr = this.split('');
    var len = arr.length;

    var from = Number(arguments[1]) || 0;
    from = (from < 0) ? Math.ceil(from) : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++) {
      if (from in arr && elt.exec(arr[from]) ) 
        return from;
    }
    return -1;
};

String.prototype.regexLastIndexOf = function(elt /*, from*/)
  {
    var arr = this.split('');
    var len = arr.length;

    var from = Number(arguments[1]);
    if (isNaN(from)) {
      from = len - 1;
    } else {
      from = (from < 0) ? Math.ceil(from) : Math.floor(from);
      if (from < 0)
        from += len;
      else if (from >= len)
        from = len - 1;
    }

    for (; from > -1; from--) {
      if (from in arr && elt.exec(arr[from]) )
        return from;
    }
    return -1;
  };

Кажется, они проходят тестовые функции, которые я предоставил в вопросе.

Очевидно, они работают, только если регулярное выражение соответствует одному символу, но этого достаточно для моей цели, так как я буду использовать его для таких вещей, как ([abc], \ s, \ W, \ D)

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

похлопывание
источник
Вау, это длинный кусок кода. Пожалуйста, проверьте мой обновленный ответ и оставьте отзыв. Спасибо.
Джейсон Бантинг
Эта реализация направлена ​​на абсолютную совместимость с lastIndexOf в Firefox и механизмом JavaScript SpiderMonkey, в том числе в некоторых случаях, которые, возможно, являются крайними. [...] в реальных приложениях вы можете рассчитывать с помощью менее сложного кода, если игнорируете эти случаи.
Пат
Сформируйте страницу mozilla :-) Я только что взял код и две строки, оставив все крайние случаи. Поскольку пара других ответов была обновлена ​​для прохождения тестов, я постараюсь сравнить их и принять наиболее эффективные. Когда у меня будет время вернуться к вопросу.
Пат
Я обновил свое решение и ценю любые отзывы или вещи, которые вызывают его сбой. Я внес изменения, чтобы исправить проблему с перекрытием, указанную MizardX (надеюсь!)
Джейсон Бантинг
2

Мне также нужна была regexIndexOfфункция для массива, поэтому я сам ее запрограммировал. Однако я сомневаюсь, что это оптимизировано, но я думаю, что это должно работать правильно.

Array.prototype.regexIndexOf = function (regex, startpos = 0) {
    len = this.length;
    for(x = startpos; x < len; x++){
        if(typeof this[x] != 'undefined' && (''+this[x]).match(regex)){
            return x;
        }
    }
    return -1;
}

arr = [];
arr.push(null);
arr.push(NaN);
arr[3] = 7;
arr.push('asdf');
arr.push('qwer');
arr.push(9);
arr.push('...');
console.log(arr);
arr.regexIndexOf(/\d/, 4);
Jakov
источник
1

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

function regexlast(string,re){
  var tokens=string.split(re);
  return (tokens.length>1)?(string.length-tokens[tokens.length-1].length):null;
}

Это имеет несколько серьезных проблем:

  1. совпадения матчей не будут отображаться
  2. возвращаемый индекс для конца матча, а не для начала (хорошо, если ваше регулярное выражение является константой)

Но с другой стороны, это намного меньше кода. Для регулярных выражений постоянной длины, которые не могут перекрываться (например, /\s\w/для нахождения границ слов), этого достаточно.

amwinter
источник
0

Для данных с разреженными совпадениями использование string.search является самым быстрым в браузерах. Каждую итерацию повторно разрезает строку на:

function lastIndexOfSearch(string, regex, index) {
  if(index === 0 || index)
     string = string.slice(0, Math.max(0,index));
  var idx;
  var offset = -1;
  while ((idx = string.search(regex)) !== -1) {
    offset += idx + 1;
    string = string.slice(idx + 1);
  }
  return offset;
}

Для плотных данных я сделал это. Он сложен по сравнению с методом execute, но для плотных данных он в 2-10 раз быстрее, чем любой другой метод, который я пробовал, и примерно в 100 раз быстрее, чем принятое решение. Основными моментами являются:

  1. Он вызывает exec для переданного регулярного выражения один раз, чтобы проверить, есть ли совпадение или выйти рано. Я делаю это с помощью (? = В аналогичном методе, но в IE проверка с exec значительно быстрее.
  2. Он создает и кэширует модифицированное регулярное выражение в формате '(r). (?!. ? r) '
  3. Выполняется новое регулярное выражение и возвращаются результаты либо этого exec, либо первого exec;

    function lastIndexOfGroupSimple(string, regex, index) {
        if (index === 0 || index) string = string.slice(0, Math.max(0, index + 1));
        regex.lastIndex = 0;
        var lastRegex, index
        flags = 'g' + (regex.multiline ? 'm' : '') + (regex.ignoreCase ? 'i' : ''),
        key = regex.source + '$' + flags,
        match = regex.exec(string);
        if (!match) return -1;
        if (lastIndexOfGroupSimple.cache === undefined) lastIndexOfGroupSimple.cache = {};
        lastRegex = lastIndexOfGroupSimple.cache[key];
        if (!lastRegex)
            lastIndexOfGroupSimple.cache[key] = lastRegex = new RegExp('.*(' + regex.source + ')(?!.*?' + regex.source + ')', flags);
        index = match.index;
        lastRegex.lastIndex = match.index;
        return (match = lastRegex.exec(string)) ? lastRegex.lastIndex - match[1].length : index;
    };

jsPerf методов

Я не понимаю цели тестов сверху. Ситуации, требующие регулярного выражения, невозможно сравнить с вызовом indexOf, который, на мой взгляд, и является главной целью создания метода. Чтобы пройти тест, имеет больше смысла использовать «xxx + (?! x)», чем настраивать способ итерации регулярного выражения.

npjohns
источник
0

Последний индекс Джейсона Бантинга не работает. Мой не оптимален, но работает.

//Jason Bunting's
String.prototype.regexIndexOf = function(regex, startpos) {
var indexOf = this.substring(startpos || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
var lastIndex = -1;
var index = this.regexIndexOf( regex );
startpos = startpos === undefined ? this.length : startpos;

while ( index >= 0 && index < startpos )
{
    lastIndex = index;
    index = this.regexIndexOf( regex, index + 1 );
}
return lastIndex;
}
Eli
источник
Можете ли вы предоставить тест, который вызывает мой сбой? Если вы обнаружили, что это не работает, предоставьте контрольный пример, почему просто скажите «это не работает» и предоставьте неоптимальное решение на месте?
Джейсон Бантинг
Ху мальчик. Ты совершенно прав. Я должен был привести пример. К сожалению, я перешел от этого кода несколько месяцев назад и понятия не имею, что это был за сбой. : - /
Эли
ну такова жизнь. :)
Джейсон Бантинг
0

Все еще нет собственных методов, которые выполняют запрошенную задачу.

Вот код, который я использую. Он имитирует поведение методов String.prototype.indexOf и String.prototype.lastIndexOf, но они также принимают RegExp в качестве аргумента поиска в дополнение к строке, представляющей значение для поиска.

Да, это довольно длинный ответ, поскольку он пытается следовать действующим стандартам настолько близко, насколько это возможно, и, конечно, содержит разумное количество комментариев JSDOC . Тем не менее, после минимизации код составляет всего 2,27 Кбайт, а один раз сжатый для передачи - только 1023 байта.

2 метода, которые это добавляет String.prototype(используя Object.defineProperty, где доступно):

  1. searchOf
  2. searchLastOf

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

/*jslint maxlen:80, browser:true */

/*
 * Properties used by searchOf and searchLastOf implementation.
 */

/*property
    MAX_SAFE_INTEGER, abs, add, apply, call, configurable, defineProperty,
    enumerable, exec, floor, global, hasOwnProperty, ignoreCase, index,
    lastIndex, lastIndexOf, length, max, min, multiline, pow, prototype,
    remove, replace, searchLastOf, searchOf, source, toString, value, writable
*/

/*
 * Properties used in the testing of searchOf and searchLastOf implimentation.
 */

/*property
    appendChild, createTextNode, getElementById, indexOf, lastIndexOf, length,
    searchLastOf, searchOf, unshift
*/

(function () {
    'use strict';

    var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1,
        getNativeFlags = new RegExp('\\/([a-z]*)$', 'i'),
        clipDups = new RegExp('([\\s\\S])(?=[\\s\\S]*\\1)', 'g'),
        pToString = Object.prototype.toString,
        pHasOwn = Object.prototype.hasOwnProperty,
        stringTagRegExp;

    /**
     * Defines a new property directly on an object, or modifies an existing
     * property on an object, and returns the object.
     *
     * @private
     * @function
     * @param {Object} object
     * @param {string} property
     * @param {Object} descriptor
     * @returns {Object}
     * @see https://goo.gl/CZnEqg
     */
    function $defineProperty(object, property, descriptor) {
        if (Object.defineProperty) {
            Object.defineProperty(object, property, descriptor);
        } else {
            object[property] = descriptor.value;
        }

        return object;
    }

    /**
     * Returns true if the operands are strictly equal with no type conversion.
     *
     * @private
     * @function
     * @param {*} a
     * @param {*} b
     * @returns {boolean}
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4
     */
    function $strictEqual(a, b) {
        return a === b;
    }

    /**
     * Returns true if the operand inputArg is undefined.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {boolean}
     */
    function $isUndefined(inputArg) {
        return $strictEqual(typeof inputArg, 'undefined');
    }

    /**
     * Provides a string representation of the supplied object in the form
     * "[object type]", where type is the object type.
     *
     * @private
     * @function
     * @param {*} inputArg The object for which a class string represntation
     *                     is required.
     * @returns {string} A string value of the form "[object type]".
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2
     */
    function $toStringTag(inputArg) {
        var val;
        if (inputArg === null) {
            val = '[object Null]';
        } else if ($isUndefined(inputArg)) {
            val = '[object Undefined]';
        } else {
            val = pToString.call(inputArg);
        }

        return val;
    }

    /**
     * The string tag representation of a RegExp object.
     *
     * @private
     * @type {string}
     */
    stringTagRegExp = $toStringTag(getNativeFlags);

    /**
     * Returns true if the operand inputArg is a RegExp.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {boolean}
     */
    function $isRegExp(inputArg) {
        return $toStringTag(inputArg) === stringTagRegExp &&
                pHasOwn.call(inputArg, 'ignoreCase') &&
                typeof inputArg.ignoreCase === 'boolean' &&
                pHasOwn.call(inputArg, 'global') &&
                typeof inputArg.global === 'boolean' &&
                pHasOwn.call(inputArg, 'multiline') &&
                typeof inputArg.multiline === 'boolean' &&
                pHasOwn.call(inputArg, 'source') &&
                typeof inputArg.source === 'string';
    }

    /**
     * The abstract operation throws an error if its argument is a value that
     * cannot be converted to an Object, otherwise returns the argument.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be tested.
     * @throws {TypeError} If inputArg is null or undefined.
     * @returns {*} The inputArg if coercible.
     * @see https://goo.gl/5GcmVq
     */
    function $requireObjectCoercible(inputArg) {
        var errStr;

        if (inputArg === null || $isUndefined(inputArg)) {
            errStr = 'Cannot convert argument to object: ' + inputArg;
            throw new TypeError(errStr);
        }

        return inputArg;
    }

    /**
     * The abstract operation converts its argument to a value of type string
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {string}
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tostring
     */
    function $toString(inputArg) {
        var type,
            val;

        if (inputArg === null) {
            val = 'null';
        } else {
            type = typeof inputArg;
            if (type === 'string') {
                val = inputArg;
            } else if (type === 'undefined') {
                val = type;
            } else {
                if (type === 'symbol') {
                    throw new TypeError('Cannot convert symbol to string');
                }

                val = String(inputArg);
            }
        }

        return val;
    }

    /**
     * Returns a string only if the arguments is coercible otherwise throws an
     * error.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @throws {TypeError} If inputArg is null or undefined.
     * @returns {string}
     */
    function $onlyCoercibleToString(inputArg) {
        return $toString($requireObjectCoercible(inputArg));
    }

    /**
     * The function evaluates the passed value and converts it to an integer.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to an integer.
     * @returns {number} If the target value is NaN, null or undefined, 0 is
     *                   returned. If the target value is false, 0 is returned
     *                   and if true, 1 is returned.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
     */
    function $toInteger(inputArg) {
        var number = +inputArg,
            val = 0;

        if ($strictEqual(number, number)) {
            if (!number || number === Infinity || number === -Infinity) {
                val = number;
            } else {
                val = (number > 0 || -1) * Math.floor(Math.abs(number));
            }
        }

        return val;
    }

    /**
     * Copies a regex object. Allows adding and removing native flags while
     * copying the regex.
     *
     * @private
     * @function
     * @param {RegExp} regex Regex to copy.
     * @param {Object} [options] Allows specifying native flags to add or
     *                           remove while copying the regex.
     * @returns {RegExp} Copy of the provided regex, possibly with modified
     *                   flags.
     */
    function $copyRegExp(regex, options) {
        var flags,
            opts,
            rx;

        if (options !== null && typeof options === 'object') {
            opts = options;
        } else {
            opts = {};
        }

        // Get native flags in use
        flags = getNativeFlags.exec($toString(regex))[1];
        flags = $onlyCoercibleToString(flags);
        if (opts.add) {
            flags += opts.add;
            flags = flags.replace(clipDups, '');
        }

        if (opts.remove) {
            // Would need to escape `options.remove` if this was public
            rx = new RegExp('[' + opts.remove + ']+', 'g');
            flags = flags.replace(rx, '');
        }

        return new RegExp(regex.source, flags);
    }

    /**
     * The abstract operation ToLength converts its argument to an integer
     * suitable for use as the length of an array-like object.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to a length.
     * @returns {number} If len <= +0 then +0 else if len is +INFINITY then
     *                   2^53-1 else min(len, 2^53-1).
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
     */
    function $toLength(inputArg) {
        return Math.min(Math.max($toInteger(inputArg), 0), MAX_SAFE_INTEGER);
    }

    /**
     * Copies a regex object so that it is suitable for use with searchOf and
     * searchLastOf methods.
     *
     * @private
     * @function
     * @param {RegExp} regex Regex to copy.
     * @returns {RegExp}
     */
    function $toSearchRegExp(regex) {
        return $copyRegExp(regex, {
            add: 'g',
            remove: 'y'
        });
    }

    /**
     * Returns true if the operand inputArg is a member of one of the types
     * Undefined, Null, Boolean, Number, Symbol, or String.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {boolean}
     * @see https://goo.gl/W68ywJ
     * @see https://goo.gl/ev7881
     */
    function $isPrimitive(inputArg) {
        var type = typeof inputArg;

        return type === 'undefined' ||
                inputArg === null ||
                type === 'boolean' ||
                type === 'string' ||
                type === 'number' ||
                type === 'symbol';
    }

    /**
     * The abstract operation converts its argument to a value of type Object
     * but fixes some environment bugs.
     *
     * @private
     * @function
     * @param {*} inputArg The argument to be converted to an object.
     * @throws {TypeError} If inputArg is not coercible to an object.
     * @returns {Object} Value of inputArg as type Object.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.9
     */
    function $toObject(inputArg) {
        var object;

        if ($isPrimitive($requireObjectCoercible(inputArg))) {
            object = Object(inputArg);
        } else {
            object = inputArg;
        }

        return object;
    }

    /**
     * Converts a single argument that is an array-like object or list (eg.
     * arguments, NodeList, DOMTokenList (used by classList), NamedNodeMap
     * (used by attributes property)) into a new Array() and returns it.
     * This is a partial implementation of the ES6 Array.from
     *
     * @private
     * @function
     * @param {Object} arrayLike
     * @returns {Array}
     */
    function $toArray(arrayLike) {
        var object = $toObject(arrayLike),
            length = $toLength(object.length),
            array = [],
            index = 0;

        array.length = length;
        while (index < length) {
            array[index] = object[index];
            index += 1;
        }

        return array;
    }

    if (!String.prototype.searchOf) {
        /**
         * This method returns the index within the calling String object of
         * the first occurrence of the specified value, starting the search at
         * fromIndex. Returns -1 if the value is not found.
         *
         * @function
         * @this {string}
         * @param {RegExp|string} regex A regular expression object or a String.
         *                              Anything else is implicitly converted to
         *                              a String.
         * @param {Number} [fromIndex] The location within the calling string
         *                             to start the search from. It can be any
         *                             integer. The default value is 0. If
         *                             fromIndex < 0 the entire string is
         *                             searched (same as passing 0). If
         *                             fromIndex >= str.length, the method will
         *                             return -1 unless searchValue is an empty
         *                             string in which case str.length is
         *                             returned.
         * @returns {Number} If successful, returns the index of the first
         *                   match of the regular expression inside the
         *                   string. Otherwise, it returns -1.
         */
        $defineProperty(String.prototype, 'searchOf', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (regex) {
                var str = $onlyCoercibleToString(this),
                    args = $toArray(arguments),
                    result = -1,
                    fromIndex,
                    match,
                    rx;

                if (!$isRegExp(regex)) {
                    return String.prototype.indexOf.apply(str, args);
                }

                if ($toLength(args.length) > 1) {
                    fromIndex = +args[1];
                    if (fromIndex < 0) {
                        fromIndex = 0;
                    }
                } else {
                    fromIndex = 0;
                }

                if (fromIndex >= $toLength(str.length)) {
                    return result;
                }

                rx = $toSearchRegExp(regex);
                rx.lastIndex = fromIndex;
                match = rx.exec(str);
                if (match) {
                    result = +match.index;
                }

                return result;
            }
        });
    }

    if (!String.prototype.searchLastOf) {
        /**
         * This method returns the index within the calling String object of
         * the last occurrence of the specified value, or -1 if not found.
         * The calling string is searched backward, starting at fromIndex.
         *
         * @function
         * @this {string}
         * @param {RegExp|string} regex A regular expression object or a String.
         *                              Anything else is implicitly converted to
         *                              a String.
         * @param {Number} [fromIndex] Optional. The location within the
         *                             calling string to start the search at,
         *                             indexed from left to right. It can be
         *                             any integer. The default value is
         *                             str.length. If it is negative, it is
         *                             treated as 0. If fromIndex > str.length,
         *                             fromIndex is treated as str.length.
         * @returns {Number} If successful, returns the index of the first
         *                   match of the regular expression inside the
         *                   string. Otherwise, it returns -1.
         */
        $defineProperty(String.prototype, 'searchLastOf', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (regex) {
                var str = $onlyCoercibleToString(this),
                    args = $toArray(arguments),
                    result = -1,
                    fromIndex,
                    length,
                    match,
                    pos,
                    rx;

                if (!$isRegExp(regex)) {
                    return String.prototype.lastIndexOf.apply(str, args);
                }

                length = $toLength(str.length);
                if (!$strictEqual(args[1], args[1])) {
                    fromIndex = length;
                } else {
                    if ($toLength(args.length) > 1) {
                        fromIndex = $toInteger(args[1]);
                    } else {
                        fromIndex = length - 1;
                    }
                }

                if (fromIndex >= 0) {
                    fromIndex = Math.min(fromIndex, length - 1);
                } else {
                    fromIndex = length - Math.abs(fromIndex);
                }

                pos = 0;
                rx = $toSearchRegExp(regex);
                while (pos <= fromIndex) {
                    rx.lastIndex = pos;
                    match = rx.exec(str);
                    if (!match) {
                        break;
                    }

                    pos = +match.index;
                    if (pos <= fromIndex) {
                        result = pos;
                    }

                    pos += 1;
                }

                return result;
            }
        });
    }
}());

(function () {
    'use strict';

    /*
     * testing as follow to make sure that at least for one character regexp,
     * the result is the same as if we used indexOf
     */

    var pre = document.getElementById('out');

    function log(result) {
        pre.appendChild(document.createTextNode(result + '\n'));
    }

    function test(str) {
        var i = str.length + 2,
            r,
            a,
            b;

        while (i) {
            a = str.indexOf('a', i);
            b = str.searchOf(/a/, i);
            r = ['Failed', 'searchOf', str, i, a, b];
            if (a === b) {
                r[0] = 'Passed';
            }

            log(r);
            a = str.lastIndexOf('a', i);
            b = str.searchLastOf(/a/, i);
            r = ['Failed', 'searchLastOf', str, i, a, b];
            if (a === b) {
                r[0] = 'Passed';
            }

            log(r);
            i -= 1;
        }
    }

    /*
     * Look for the a among the xes
     */

    test('xxx');
    test('axx');
    test('xax');
    test('xxa');
    test('axa');
    test('xaa');
    test('aax');
    test('aaa');
}());
<pre id="out"></pre>

Xotic750
источник
0

Если вы ищете очень простой поиск lastIndex с RegExp и вам все равно, будет ли он повторять lastIndexOf до последней детали, это может привлечь ваше внимание.

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

interface String {
  reverse(): string;
  lastIndex(regex: RegExp): number;
}

String.prototype.reverse = function(this: string) {
  return this.split("")
    .reverse()
    .join("");
};

String.prototype.lastIndex = function(this: string, regex: RegExp) {
  const exec = regex.exec(this.reverse());
  return exec === null ? -1 : this.length - 1 - exec.index;
};
Reijo
источник
0

Я использовал String.prototype.match(regex)метод, который возвращает строковый массив всех найденных совпадений заданного regexв строке (подробнее см. Здесь ):

function getLastIndex(text, regex, limit = text.length) {
  const matches = text.match(regex);

  // no matches found
  if (!matches) {
    return -1;
  }

  // matches found but first index greater than limit
  if (text.indexOf(matches[0] + matches[0].length) > limit) {
    return -1;
  }

  // reduce index until smaller than limit
  let i = matches.length - 1;
  let index = text.lastIndexOf(matches[i]);
  while (index > limit && i >= 0) {
    i--;
    index = text.lastIndexOf(matches[i]);
  }
  return index > limit ? -1 : index;
}

// expect -1 as first index === 14
console.log(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g, 10));

// expect 29
console.log(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g));

wfreude
источник
0
var mystring = "abc ab a";
var re  = new RegExp("ab"); // any regex here

if ( re.exec(mystring) != null ){ 
   alert("matches"); // true in this case
}

Используйте стандартные регулярные выражения:

var re  = new RegExp("^ab");  // At front
var re  = new RegExp("ab$");  // At end
var re  = new RegExp("ab(c|d)");  // abc or abd
user984003
источник
-2

Ну, так как вы просто ищете, чтобы соответствовать позиции персонажа , регулярное выражение возможно излишне.

Я предполагаю, что все, что вы хотите, вместо того, чтобы «найти первым из этих символов», просто найти первым из этих символов.

Это, конечно, простой ответ, но он выполняет то, что намеревается сделать ваш вопрос, хотя и без части регулярного выражения (потому что вы не выяснили, почему конкретно это должно быть регулярное выражение)

function mIndexOf( str , chars, offset )
{
   var first  = -1; 
   for( var i = 0; i < chars.length;  i++ )
   {
      var p = str.indexOf( chars[i] , offset ); 
      if( p < first || first === -1 )
      {
           first = p;
      }
   }
   return first; 
}
String.prototype.mIndexOf = function( chars, offset )
{
   return mIndexOf( this, chars, offset ); # I'm really averse to monkey patching.  
};
mIndexOf( "hello world", ['a','o','w'], 0 );
>> 4 
mIndexOf( "hello world", ['a'], 0 );
>> -1 
mIndexOf( "hello world", ['a','o','w'], 4 );
>> 4
mIndexOf( "hello world", ['a','o','w'], 5 );
>> 6
mIndexOf( "hello world", ['a','o','w'], 7 );
>> -1 
mIndexOf( "hello world", ['a','o','w','d'], 7 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 10 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 11 );
>> -1
Кент Фредрик
источник
Просто комментарий об исправлении обезьян - хотя я знаю о его проблемах - вы думаете, загрязнение глобального пространства имен лучше? Это не похоже на то, что конфликты символов в ОБА случаях не могут произойти, и, в основном, они подвергаются рефакторингу / ремонту таким же образом, если возникает проблема.
Питер Бейли
Ну, мне нужно искать \ s, а в некоторых случаях \ W и надеялся, что мне не придется перечислять все возможности.
Пат
BaileyP: вы можете обойти эту проблему без загрязнения глобального пространства имен, например: см. JQuery, например. использовать эту модель. один объект для проекта, ваши вещи идут внутри него. Mootools оставил неприятный вкус во рту.
Кент Фредрик
Также следует отметить, что я никогда не пишу код, как я написал там. пример был упрощен по причинам использования.
Кент Фредрик