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

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

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

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

и

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

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

Edit:
Это оказалось сложнее, чем я думал, поэтому я написал небольшую тестовую функцию для проверки всех предоставленных решений ... предполагается, что 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

// Ищите а среди хэсов
тест ( 'ххх');
тест ( 'ахх');
тест ( 'хах');
тест ( 'XXA'); * * один тысяча двадцать-одна тест ( 'ах');
тест ( 'хаа');
тест ( 'а');
тест ( 'ааа');

Ответы [ 16 ]

163 голосов
/ 08 ноября 2008

Экземпляры конструктора 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;
}
122 голосов
/ 08 ноября 2008

Комбинируя несколько уже упомянутых подходов (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(). Пожалуйста, дайте мне знать, если это все еще не удается и при каких обстоятельствах.


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

32 голосов
/ 29 января 2014

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

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);
12 голосов
/ 13 июля 2015

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

str.search(regex)

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

6 голосов
/ 08 ноября 2008

На основании ответа 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;
}
6 голосов
/ 08 ноября 2008

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

str.substr(i).match(/[abc]/);
4 голосов
/ 08 ноября 2008

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 уже не совпадает в конце строки.

4 голосов
/ 08 ноября 2008

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

<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>

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

2 голосов
/ 01 сентября 2012

Мне нужна была также 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);
2 голосов
/ 08 ноября 2008

После того, как все предложенные решения так или иначе не прошли мои тесты, (отредактируйте: некоторые были обновлены, чтобы пройти тесты после того, как я написал это), я нашел реализацию 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)

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

...