Is there a version of JavaScript’s String.indexOf() that allows for regular expressions?

Is there a version of JavaScript’s String.indexOf() that allows for regular expressions?

In javascript, is there an equivalent of String.indexOf() that takes a regular expression instead of a string for the first first parameter while still allowing a second parameter ?
I need to do something like
str.indexOf(/[abc]/ , i);

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

While String.search() takes a regexp as a parameter it does not allow me to specify a second argument!
Edit:
This turned out to be harder than I originally thought so I wrote a small test function to test all the provided solutions… it assumes regexIndexOf and regexLastIndexOf have been added to the String object.
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)]) ;
}
}

and I am testing as follow to make sure that at least for one character regexp, the result is the same as if we used indexOf
//Look for the a among the xes
test(‘xxx’);
test(‘axx’);
test(‘xax’);
test(‘xxa’);
test(‘axa’);
test(‘xaa’);
test(‘aax’);
test(‘aaa’);

Solutions/Answers:

Solution 1:

Combining a few of the approaches already mentioned (the indexOf is obviously rather simple), I think these are the functions that will do the trick:

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

Obviously, modifying the built-in String object would send up red flags for most people, but this may be one time when it is not that big of a deal; simply be aware of it.

Related:  From AngularJS to Flux - The React Way

UPDATE: Edited regexLastIndexOf() so that is seems to mimic lastIndexOf() now. Please let me know if it still fails and under what circumstances.


UPDATE: Passes all tests found on in comments on this page, and my own. Of course, that doesn’t mean it’s bulletproof. Any feedback appreciated.

Solution 2:

Instances of the String constructor have a .search() method which accepts a RegExp and returns the index of the first match.

To start the search from a particular position (faking the second parameter of .indexOf()) you can slice off the first i characters:

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

But this will get the index in the shorter string (after the first part was sliced off) so you’ll want to then add the length of the chopped off part (i) to the returned index if it wasn’t -1. This will give you the index in the original string:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}

Solution 3:

I have a short version for you. It works well for me!

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

And if you want a prototype version:

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 : if you want to add support for 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;
}

To use it, as simple as this:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);

Solution 4:

Use:

str.search(regex)

See the documentation here.

Related:  What are modern uses of script type=“text/html” and is this example considered good use?

Solution 5:

You could use substr.

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

Solution 6:

Based on BaileyP’s answer. The main difference is that these methods return -1 if the pattern can’t be matched.

Edit: Thanks to Jason Bunting’s answer I got an idea. Why not modify the .lastIndex property of the regex? Though this will only work for patterns with the global flag (/g).

Edit: Updated to pass the test-cases.

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