정규 표현식을 허용하는 JavaScript의 String.indexOf () 버전이 있습니까?


214

자바 스크립트에서 첫 번째 첫 번째 매개 변수의 문자열 대신 정규 표현식을 사용하면서 두 번째 매개 변수를 허용하는 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를 사용하는 것과 결과가 동일한 지 확인하기 위해 다음과 같이 테스트하고 있습니다.

// xes
test ( 'xxx'); 중 a를 찾으십시오 .
테스트 ( 'axx');
테스트 ( 'xax');
테스트 ( 'xxa');
테스트 ( 'axa');
테스트 ( 'xaa');
테스트 ( 'aax');
테스트 ( 'aaa');


|inside [ ]는 리터럴 문자와 일치합니다 |. 당신은 아마 의미했다 [abc].
Markus Jarderot

네, 그렇습니다. 고맙습니다. 고칠 것이지만 정규 표현식 자체는 관련이 없습니다.
Pat

의견을 보내 주셔서 감사합니다. Pat.
Jason Bunting

더 간단하고 효과적인 접근 방식은 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의지는 마지막의 인덱스 반환 겹치지 않는 경기.
Markus Jarderot

죄송합니다. 거대한 정규 표현식 사람이 아닙니다. 저를 실패하게 만드는 예를 들어 주시겠습니까? 더 많은 것을 배울 수있어서 감사하지만 귀하의 답변은 다른 사람이 무지한 사람에게는 도움이되지 않습니다. :)
Jason Bunting

Jason 방금 질문에서 테스트 할 기능을 추가했습니다. 다음과 같은 'axx'.lastIndexOf ('a ', 2)! ='axx'.regexLastIndexOf (/ a /, 2)
Pat

2
regex.lastIndex = result.index + 1;대신에 사용하는 것이 더 효율적이라고 생각합니다 regex.lastIndex = ++nextStop;. 결과를 잃지 않고 다음 경기를 훨씬 더 빨리 진행할 것입니다.
Gedrox

1
npm에서 가져 오려면이 두 가지 유틸리티 기능이 NPM에 있습니다. npmjs.com/package/index-of-regex
Capaj

185

String생성자의 인스턴스 에는 RegExp를 허용하고 첫 번째 일치하는 인덱스를 반환 하는 .search()메서드 가 있습니다.

특정 위치에서 검색을 시작하려면 (의 두 번째 매개 변수에 해당 .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 ()는 정규 표현식을 매개 변수로 사용하지만 두 번째 인수를 지정할 수는 없습니다!
Pat

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

편집 : 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);

이것은 실제로 좋은 트릭입니다. startIndex평소 indeoxOf와 같이 매개 변수를 취하고 확장하도록 확장하면 좋을 lastIndexOf것입니다.
Robert Koritnik

@RobertKoritnik-지원 startIndex(또는 fromIndex)에 대한 답변을 편집했습니다 . 그것이 도움이되기를 바랍니다!
pmrotule

lastIndexOfRegexfromIndex결과에 값을 다시 추가해야합니다 .
Peter

다음 시나리오에서 알고리즘이 중단됩니다. "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));7이되어야 할 때 결과는 1이됩니다. 단어의 시작 여부에 상관없이 indexOf는 "romeo"가 처음 나타날 때 검색하기 때문입니다.
KorelK

13

사용하다:

str.search(regex)

여기 에서 설명서를 참조 하십시오.


11
@OZZIE : 아뇨. 그것은 기본적으로의 글렌의 대답 이 없습니다 제외하고 (150 ~와 upvotes) 아무런 설명 무엇이든지 않습니다 지원하지 이외의 시작 위치 0및 게시 ... 칠년 이상.
ccjmne

7

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

이것은 지금까지 가장 유망한 것으로 보입니다 (몇 가지 sytax 수정 후) :-) 가장자리 조건에 대한 몇 가지 테스트 만 실패했습니다. 'axx'.lastIndexOf ('a ', 0)! ='axx'.regexLastIndexOf (/ a /, 0) ... 것들을 고칠 수 있는지 살펴보고 있습니다
Pat

6

substr을 사용할 수 있습니다.

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

O'Reilly가 발행 한 잘 알려진 JavaScript 책에서 : "substr은 ECMAScript에 의해 표준화되지 않았으므로 더 이상 사용되지 않습니다." 그러나 나는 당신이 얻는 것에 대한 기본 아이디어를 좋아합니다.
Jason Bunting

1
그것은 문제가 아닙니다. 정말로 염려된다면 String.substring ()을 대신 사용하십시오-수학을 조금 다르게해야합니다. 게다가, JavaScript는 모국어로 100 %가되어서는 안됩니다.
피터 베일리

ECMAScript 표준을 준수하기 위해 substr을 구현하지 않는 구현에 대해 코드를 실행하면 문제가 발생할 수 있습니다. 물론, 문자열을 부분 문자열로 바꾸는 것은 그리 어렵지 않지만 이것을 인식하는 것이 좋습니다.
Jason Bunting

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>

나는이 방법을 완전히 테스트하지는 않았지만 지금까지는 효과가있는 것 같습니다.


이러한 상황을 처리하도록 업데이트 됨
Peter Bailey

이 답변을 수락하려고 할 때마다 새로운 사례를 찾습니다! 이것들은 다른 결과를 낳습니다! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat

물론 str.lastIndexOf는 패턴에 대해 강제 변환을 수행하여 문자열로 변환합니다. 입력에서 "/ [d] /"문자열을 찾을 수 없으므로 반환 된 -1이 실제로 정확합니다.
Peter Bailey

알았다. String.lastIndexOf ()에 대한 사양을 읽은 후-그 인수가 어떻게 작동했는지 오해했습니다. 이 새 버전은이를 처리해야합니다.
Peter Bailey

여전히 옳지 않은 것이 있지만 늦어지고 있습니다 ... 테스트 케이스를 받아 아침에 고칠 것입니다. 지금까지 문제가 발생하여 죄송합니다.
Pat

2

제안 된 솔루션이 모두 테스트에 실패하거나 실패한 후 (편집 : 일부는이를 작성한 후 테스트를 통과하도록 업데이트되었습니다) Array.indexOfArray.lastIndexOf에 대한 mozilla 구현을 찾았 습니다.

다음과 같이 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)

누군가 정규 표현식에서 작동하는 더 나은 / 더 빠른 / 더 깨끗한 / 더 일반적인 구현을 제공하는 경우 질문을 계속 모니터링 할 것입니다.


와우, 그것은 긴 코드입니다. 업데이트 된 답변을 확인하고 피드백을 제공하십시오. 감사.
Jason Bunting

이 구현은 Firefox 및 SpiderMonkey JavaScript 엔진에서 lastIndexOf와의 절대 호환성을 목표로합니다. [...] 실제 응용 프로그램에서는 이러한 경우를 무시하면 덜 복잡한 코드로 계산할 수 있습니다.
Pat

mozilla 페이지를 작성하십시오 :-) 방금 코드 광고를 두 줄로 바꿔서 모든 경우를 남겨 두었습니다. 다른 두 가지 답변이 테스트를 통과하도록 업데이트되었으므로 벤치마킹을 시도하고 가장 효율적인 방법을 채택하겠습니다. 문제를 다시 방문 할 시간이있을 때
Pat

솔루션을 업데이트하고 실패한 피드백이나 사물을 고맙게 생각합니다. 나는 중복 문제가 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

간단한 경우에는 split을 사용하여 역방향 검색을 단순화 할 수 있습니다.

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

조밀 한 데이터를 위해 이것을 만들었습니다. 실행 방법에 비해 복잡하지만 밀도가 높은 데이터의 경우 다른 모든 방법보다 2-10 배 빠르며 허용되는 솔루션보다 약 100 배 빠릅니다. 요점은 다음과 같습니다.

  1. 한 번 전달 된 정규식에서 exec를 호출하여 일치하는 항목이 있는지 확인하거나 조기에 종료합니다. 비슷한 방법으로 (? =를 사용 하여이 작업을 수행하지만 exec로 IE를 검사하는 것이 훨씬 빠릅니다.
  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)'를 사용하는 것이 좋습니다.


0

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

내 실패를 일으키는 테스트를 제공 할 수 있습니까? 작동하지 않는 경우 테스트 사례를 제공하십시오. 왜 "작동하지 않습니다"라고 말하고 최적이 아닌 솔루션을 제공합니까?
Jason Bunting

후아 당신 말이 맞아요 나는 예를 제시해야했다. 불행히도 나는 몇 달 전에이 코드에서 나왔고 실패 사례가 무엇인지 전혀 모른다. :-/
Eli

글쎄요, 그런 삶입니다. :)
Jason Bunting

0

요청 된 작업을 수행하는 기본 메소드는 여전히 없습니다.

사용중인 코드는 다음과 같습니다. String.prototype.indexOfString.prototype.lastIndexOf 메서드 의 동작을 모방 하지만 검색 할 값을 나타내는 문자열 외에도 RegExp를 검색 인수로 허용합니다.

그렇습니다. 가능한 한 현재 표준을 최대한 가깝게 따르려고 노력하고 있으며, 상당한 양의 JSDOC 의견이 포함되어 있습니다 . 그러나 일단 축소되면 코드는 2.27k에 불과하며 전송을 위해 gzipped되면 1023 바이트에 불과합니다.

이것이 추가되는 두 가지 방법 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>


0

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

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

원숭이 패치에 대한 의견-문제를 알고 있지만 전역 네임 스페이스를 오염시키는 것이 더 낫다고 생각하십니까? 두 경우 모두에서 발생하는 기호 충돌과 같지 않으며 문제가 발생할 경우 기본적으로 동일한 방식으로 리팩토링 / 수리됩니다.
피터 베일리

글쎄, 나는 \ s를 찾고 어떤 경우에는 \ W를 검색해야하며 모든 가능성을 열거하지 않아도되기를 바랐습니다.
Pat

BaileyP : 전역 네임 스페이스 오염없이이 문제를 해결할 수 있습니다 (예 : jQuery 참조). 그 모델을 사용하십시오. 프로젝트를위한 하나의 객체, 당신의 물건은 그 안에 들어갑니다. Mootools는 내 입안에 나쁜 맛을 남겼습니다.
Kent Fredric

또한 내가 거기에 쓴 것처럼 절대 코드하지 않습니다. 사용 사례로 인해 예제가 단순화되었습니다.
Kent Fredric
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.