正規表現を許可するJavaScript String.indexOf()のバージョンはありますか?
-
07-07-2019 - |
質問
javascriptには、最初の最初のパラメーターに文字列ではなく正規表現を使用し、2番目のパラメーターを許可するString.indexOf()に相当するものがありますか?
次のようなことをする必要があります
str.indexOf(/[abc]/ , i);
and
str.lastIndexOf(/[abc]/ , i);
String.search()はパラメーターとして正規表現を取りますが、2番目の引数を指定することはできません!
編集:
これは当初考えていたよりも難しいことが判明したため、提供されたすべてのソリューションをテストする小さなテスト関数を作成しました... 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)]) ;
}
}
そして次のようにテストして、少なくとも1文字の正規表現について、indexOfを使用した場合と同じ結果になることを確認しています
// xesの中からaを探す
test( 'xxx');
test( 'axx');
test( 'xax');
test( 'xxa');
test( 'axa');
test( 'xaa');
test( 'aax');
test( 'aaa');
解決
既に述べたアプローチのいくつかを組み合わせて(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オブジェクトを変更すると、ほとんどの人に赤い旗が送られますが、それほど大きな問題ではない場合もあります。気をつけてください。
UPDATE: regexLastIndexOf()
を編集して、現在 lastIndexOf()
を模倣しているようです。それでも失敗するかどうか、どのような状況であるかを教えてください。
UPDATE:このページのコメントで見つかったすべてのテスト、および自分のテストに合格します。もちろん、それは防弾という意味ではありません。フィードバックをお願いします。
他のヒント
String
コンストラクターのインスタンスには、 .search()
メソッド。RegExpを受け入れ、最初に一致したインデックスを返します。
特定の位置から検索を開始するには( .indexOf()
の2番目のパラメーターを偽装)、最初の i
を slice
することができます。文字:
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;
}
私はあなたのために短いバージョンを持っています。私にとってはうまくいきます!
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);
substrを使用できます。
str.substr(i).match(/[abc]/);
BaileyPの回答に基づきます。主な違いは、パターンが一致しない場合、これらのメソッドは -1
を返すことです。
編集: Jason Buntingの回答に感謝します。正規表現の .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;
}
ネイティブではありませんが、確かにこの機能を追加できます
<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>
これらのメソッドを完全にはテストしませんでしたが、今のところ機能しているようです。
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
が文字列の末尾で既に一致していない限り、負の先読みを使用して最後の出現を検索しています。
提案されたすべてのソリューションが何らかの方法でテストに失敗した後(編集:これを書いた後にテストに合格するように更新されたものもあります)、 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;
};
質問で提供したテスト関数に合格したようです。
明らかに、正規表現が1文字に一致する場合にのみ機能しますが、([abc]、\ s、\ W、\ D)のようなものに使用するので、それは私の目的には十分です。
誰かが正規表現で動作するより良い/より速い/よりきれいな/より一般的な実装を提供する場合に備えて、質問を監視し続けます。
配列にも 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);
特定の単純なケースでは、splitを使用して後方検索を簡素化できます。
function regexlast(string,re){
var tokens=string.split(re);
return (tokens.length>1)?(string.length-tokens[tokens.length-1].length):null;
}
これにはいくつかの深刻な問題があります:
- 重複する一致は表示されません
- 返されるインデックスは、開始ではなく、一致の終了用です(正規表現が定数の場合は問題ありません)
しかし、明るい面では、コードが少なくなります。重複できない固定長の正規表現(単語境界を見つけるための / \ s \ w /
など)の場合、これで十分です。
まばらに一致するデータの場合、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を使用したチェックが劇的に高速になります。
- 変更された正規表現を '(r)。(?!。?r)'の形式で構築およびキャッシュします
-
新しい正規表現が実行され、その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 + '
テストの目的がわからない。正規表現を必要とする状況は、indexOfの呼び出しと比較することは不可能です。そもそもメソッドを作成するポイントだと思います。テストに合格するには、正規表現の反復方法を調整するよりも、「xxx +(?! x)」を使用する方が理にかなっています。
+ 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; };テストの目的がわからない。正規表現を必要とする状況は、indexOfの呼び出しと比較することは不可能です。そもそもメソッドを作成するポイントだと思います。テストに合格するには、正規表現の反復方法を調整するよりも、「xxx +(?! x)」を使用する方が理にかなっています。
Jason Buntingの最後のインデックスは機能しません。鉱山は最適ではありませんが、機能します。
//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;
}
要求されたタスクを実行するネイティブメソッドはまだありません。
これは私が使用しているコードです。 String.prototype.indexOf および String.prototype.lastIndexOf メソッドがまた、検索する値を表す文字列に加えて、検索引数としてRegExpを受け入れます。
はい、それは可能な限り近い現在の標準に準拠しようとするので、答えはかなり長いです。もちろん、合理的な量の JSDOC コメント。ただし、コードを縮小すると、コードは2.27kになり、送信用にgzip圧縮すると、1023バイトになります。
これが String.prototype
に追加する2つのメソッド( Object.defineProperty が利用可能な場合):
-
searchOf
-
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]*)
<pre id="out"></pre>
, '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'); }()); <*>
RegExpを使用した非常に単純なlastIndexルックアップを探していて、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;
};
まあ、あなたはキャラクターの位置を一致させようとしているだけなので、正規表現は多すぎるかもしれません。
「この文字の最初を見つける」のではなく、あなたが望むのはすべてだと思います。 、これらの文字の最初を見つけてください。
これはもちろん単純な答えですが、正規表現の部分はありませんが(あなたが具体的に正規表現である必要がある理由を明確にしなかったため)、質問の目的を実行します
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