Чи існує версія String.indexOf () JavaScript, яка дозволяє регулярні вирази?


214

Чи існує в javascript еквівалент String.indexOf (), який приймає регулярний вираз замість рядка для першого першого параметра, одночасно дозволяючи другий параметр?

Мені потрібно зробити щось на кшталт

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

і

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

Хоча String.search () приймає регулярний параметр як параметр, він не дозволяє мені вказати другий аргумент!

Редагувати:
Це виявилося складніше, ніж я спочатку вважав, тому я написав невелику тестову функцію, щоб перевірити всі надані рішення ... він передбачає, що в об'єкт String були додані regexIndexOf і regexLastIndexOf.

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)]) ;
    }
}

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

// Шукайте
тест a серед xes ('xxx');
тест ('axx');
тест ('xax');
тест ('xxa');
тест ('axa');
тест ('xaa');
тест ('aax');
тест ('aaa');


|всередині [ ]відповідає буквальному характеру |. Ви , ймовірно , мав в виду [abc].
Маркус Джардеро

так, дякую, ти маєш рацію, я виправлю це, але сам regexp не має значення ...
Пат

Оновлено мою відповідь Пат, дякую за будь-які відгуки.
Джейсон Бантінг

Я знайшов більш простий та ефективний підхід - просто використовувати 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)
Пат

2
Я думаю, що це більш ефективно використовувати regex.lastIndex = result.index + 1;замість цього regex.lastIndex = ++nextStop;. Він буде переходити до наступного матчу набагато швидше, сподіваючись, не втрачаючи жодного результату.
Гедрокс

1
Якщо ви віддаєте перевагу витягувати його з npm, ці дві функції утиліти
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 () бере параметр regexp як параметр, він не дозволяє мені вказати другий аргумент!
Пат

14
str.substr (i) .search (/ re /)
Glenn

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

EDIT : якщо ви хочете додати підтримку відIndex

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

Це насправді приємна хитрість. Було б чудово, якщо ви розширите його, щоб також прийняти startIndexпараметр як завжди indeoxOfі lastIndexOfзробити.
Роберт Коритник

@RobertKoritnik - я відредагував свою відповідь на підтримку startIndex(або fromIndex). Сподіваюся, це допомагає!
pmrotule

lastIndexOfRegexтакож слід додати значення fromIndexрезультату.
Пітер

Ваш алгоритм розпадеться за наступним сценарієм: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Результат буде 1, коли має бути 7, тому що indexOf вперше буде шукати "romeo", незалежно від того, знаходиться він на початку слова чи ні.
КорелК

13

Використання:

str.search(regex)

Дивіться документацію тут.


11
@OZZIE: Ні, не дуже. Це, в основному , відповідь Глена (з ~ 150 оновленими результатами), за винятком того, що він не має жодного пояснення , не підтримує стартову позицію, окрім 0, та був опублікований ... сім років потому.
ccjmne

7

Виходячи з відповіді БейліП. Основна відмінність полягає в тому, що ці методи повертаються, -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 (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Я розглядаю, чи зможу я виправити ці випадки
Пат

6

Ви можете використовувати substr.

str.substr(i).match(/[abc]/);

З відомої книги JavaScript, опублікованої O'Reilly: "substr не був стандартизований ECMAScript і тому застарів". Але мені подобається основна ідея, за якою ви стикаєтесь.
Джейсон Бантінг

1
Це не питання. Якщо ви дійсно турбуєтесь про це, використовуйте натомість String.substring () - ви просто повинні зробити математику трохи інакше. Крім того, JavaScript не повинен на 100% дотримуватися його материнської мови.
Пітер Бейлі

Це не проблема - якщо ваш код працює проти реалізації, яка не реалізує substr, оскільки вони хочуть дотримуватися стандартів ECMAScript, у вас виникнуть проблеми. Зрозуміло, замінити його на підрядку не так вже й важко, але добре бути в курсі цього.
Джейсон Бантінг

1
У момент виникнення проблем у вас є дуже дуже прості рішення. Я вважаю, що коментарі слушні, але голосування "за" - було педантичним.
VoronoiPotato

Чи можете ви відредагувати свою відповідь, щоб вказати діючий демо-код?
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вже не збігався в кінці рядка.


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>

Я не повністю перевіряв ці методи, але вони, здається, працюють поки що.


Оновлено для вирішення цих справ
Пітер Бейлі

кожного разу, коли я збираюся прийняти цю відповідь, я знаходжу новий випадок! Вони дають різні результати! попередження ([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);

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/для пошуку меж слова), це досить добре.


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. Новий регулярний вираз виконується і результати від того, що виконується, або першого виконання, повертаються;

    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)", ніж коригувати те, як регексується ітерація.


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

Чи можете ви надати тест, який спричиняє збій шахти? Якщо ви виявили, що це не працює, наведіть тестовий випадок, чому просто сказати «він не працює» та запропонувати не оптимальне рішення?
Джейсон Бантінг

Ху хлопчик. Ви абсолютно праві. Я мав би навести приклад. На жаль, я перейшов від цього коду місяці тому і не маю поняття, що таке справа несправності. : - /
Елі

ну таке життя. :)
Джейсон Бантінг

0

Досі не існує нативних методів, які виконують задане завдання.

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

Так, це досить довго, оскільки йдеться про відповідь, оскільки вона намагається максимально наблизитись до сучасних стандартів і, звичайно, містить розумну кількість JSDOC коментарів . Однак, щойно зміцнюється, код становить лише 2,27 кб, а один раз gzipped для передачі - це лише 1023 байти.

Два способи, до яких додається це String.prototype(використовуючи Object.defineProperty, якщо є):

  1. searchOf
  2. searchLastOf

Він проходить всі випробування, які опублікував ОП, і крім того, я досить ретельно перевіряв процедури у своєму щоденному використанні, і намагався бути впевненим, що вони працюють у різних середовищах, але відгуки / проблеми завжди вітаються.

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


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

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


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

-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

Лише коментар щодо виправлення мавп - хоча я знаю про його проблеми - ви вважаєте, що забруднення глобального простору імен краще? Це не так, як конфлікти символів у випадках, які НЕ можуть відбуватися, і, в основному, реконструюються / відновлюються таким же чином, якщо виникає проблема.
Пітер Бейлі

Ну, мені потрібно шукати \ з, а в деяких випадках \ W і сподівався, що мені не доведеться перераховувати всі можливості.
Пт

BaileyP: Ви можете подолати цю проблему без забруднення глобального простору імен, тобто: див., Наприклад, jQuery. використовувати цю модель. один об'єкт для проекту, ваші речі надходять всередину нього. Mootools залишив неприємний смак у мене в роті.
Кент Фредрік

також слід зазначити, що я ніколи не кодую, як я написав там. приклад спрощено з причин використання.
Кент Фредрік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.