Có phiên bản String.indexOf () của JavaScript cho phép biểu thức chính quy không?


214

Trong javascript, có tương đương với String.indexOf () có biểu thức chính quy thay vì chuỗi cho tham số đầu tiên đầu tiên trong khi vẫn cho phép tham số thứ hai không?

Tôi cần phải làm một cái gì đó như

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

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

Trong khi String.search () lấy regrec làm tham số, nó không cho phép tôi chỉ định đối số thứ hai!

Chỉnh sửa:
Điều này hóa ra khó hơn tôi nghĩ ban đầu nên tôi đã viết một hàm kiểm tra nhỏ để kiểm tra tất cả các giải pháp được cung cấp ... nó giả sử regexIndexOf và regexLastIndexOf đã được thêm vào đối tượng 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)]) ;
    }
}

và tôi đang thử nghiệm như sau để đảm bảo rằng ít nhất cho một regrec ký tự, kết quả giống như khi chúng ta sử dụng indexOf

// Tìm kiếm một trong số các
thử nghiệm xes ('xxx');
thử nghiệm ('axx');
thử nghiệm ('xax');
thử nghiệm ('xxa');
thử nghiệm ('axa');
kiểm tra ('xaa');
kiểm tra ('aax');
kiểm tra ('aaa');


|bên trong [ ]phù hợp với các nhân vật theo nghĩa đen |. Bạn có thể có nghĩa [abc].
Markus Jarderot

vâng, cảm ơn bạn đã đúng, tôi sẽ sửa nó nhưng bản thân regrec không liên quan ...
Pat

Cập nhật câu trả lời của tôi Pat, cảm ơn cho bất kỳ thông tin phản hồi.
Jason Bunting

Tôi tìm thấy một cách tiếp cận đơn giản và hiệu quả hơn là chỉ sử dụng string.match (/ [AZ] /). Nếu không có nhiều, phương thức trả về null, nếu không, bạn nhận được một đối tượng, bạn có thể khớp chỉ mục (/ [AZ] /). Để lấy chỉ mục của chữ in hoa đầu tiên
Syler

Câu trả lời:


129

Kết hợp một vài cách tiếp cận đã được đề cập (indexOf rõ ràng khá đơn giản), tôi nghĩ đây là những chức năng sẽ thực hiện thủ thuật:

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

Rõ ràng, việc sửa đổi đối tượng Chuỗi tích hợp sẽ gửi cờ đỏ cho hầu hết mọi người, nhưng đây có thể là một lần khi nó không phải là vấn đề lớn; chỉ đơn giản là nhận thức được nó.


CẬP NHẬT: Đã chỉnh sửa regexLastIndexOf()để dường như bắt chước lastIndexOf()ngay bây giờ. Xin vui lòng cho tôi biết nếu nó vẫn thất bại và trong hoàn cảnh nào.


CẬP NHẬT: Vượt qua tất cả các bài kiểm tra được tìm thấy trong các nhận xét trên trang này và của riêng tôi. Tất nhiên, điều đó không có nghĩa là nó chống đạn. Bất kỳ thông tin phản hồi đánh giá cao.


Bạn regexLastIndexOfsẽ chỉ trả lại chỉ mục của trận đấu không chồng chéo cuối cùng .
Markus Jarderot

Xin lỗi, không phải là một anh chàng regex HUGE - bạn có thể cho tôi một ví dụ khiến tôi thất bại không? Tôi đánh giá cao việc có thể tìm hiểu thêm, nhưng phản hồi của bạn không giúp được ai đó không biết gì như tôi. :)
Jason Bunting

Jason Tôi chỉ cần thêm một số chức năng để kiểm tra trong câu hỏi. này là không (trong số những xét nghiệm khác) sau 'axx'.lastIndexOf (' a '2) =!' axx'.regexLastIndexOf (/ a /, 2)
Pat

2
Tôi nghĩ rằng nó hiệu quả hơn để sử dụng regex.lastIndex = result.index + 1;thay vì regex.lastIndex = ++nextStop;. Nó sẽ tiến tới trận đấu tiếp theo hy vọng nhanh hơn nhiều mà không mất bất kỳ kết quả nào.
Gedrox

1
Nếu bạn muốn kéo nó từ npm, hai chức năng sử dụng này hiện có trên NPM là: npmjs.com/package/index-of-regex
Capaj

185

Các thực thể của hàm Stringtạo có một .search()phương thức chấp nhận RegExp và trả về chỉ mục của kết quả khớp đầu tiên.

Để bắt đầu tìm kiếm từ một vị trí cụ thể (giả mạo tham số thứ hai của .indexOf()), bạn có thể slicetắt các iký tự đầu tiên :

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

Nhưng điều này sẽ đưa chỉ mục trong chuỗi ngắn hơn (sau khi phần đầu tiên bị cắt) vì vậy bạn sẽ muốn sau đó thêm độ dài của phần bị băm nhỏ ( i) vào chỉ mục được trả về nếu không -1. Điều này sẽ cung cấp cho bạn chỉ mục trong chuỗi ban đầu:

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

1
từ câu hỏi: Mặc dù String.search () lấy regrec làm tham số nhưng nó không cho phép tôi chỉ định đối số thứ hai!
Pat

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

6
Giải pháp tuyệt vời, tuy nhiên đầu ra hơi khác một chút. indexOf sẽ trả về một số từ đầu (không phụ thuộc vào phần bù), trong khi điều này sẽ trả về vị trí từ phần bù. Vì vậy, để tương đương, bạn sẽ muốn một cái gì đó giống như thế này:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger

39

Tôi có một phiên bản ngắn cho bạn. Nó hoạt động tốt cho tôi!

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

Và nếu bạn muốn có một phiên bản nguyên mẫu:

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 : nếu bạn muốn thêm hỗ trợ cho từ 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;
}

Để sử dụng nó, đơn giản như thế này:

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

Đây thực sự là một mẹo hay. WOuld sẽ rất tuyệt nếu bạn mở rộng nó để lấy startIndextham số như bình thường indeoxOflastIndexOflàm.
Robert Koritnik

@RobertKoritnik - Tôi đã chỉnh sửa câu trả lời của mình để hỗ trợ startIndex(hoặc fromIndex). Hy vọng nó giúp!
pmrotule

lastIndexOfRegexcũng nên thêm lại giá trị của fromIndexkết quả.
Peter

Thuật toán của bạn sẽ bị vỡ trong kịch bản sau: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Kết quả sẽ là 1 khi nó là 7, vì indexOf sẽ tìm lần đầu tiên "romeo" xuất hiện, bất kể nó có ở đầu từ hay không.
KorelK


7

Dựa trên câu trả lời của BaileyP. Sự khác biệt chính là các phương thức này trả về -1nếu mẫu không thể khớp.

Chỉnh sửa: Nhờ câu trả lời của Jason Bunting, tôi có một ý tưởng. Tại sao không sửa đổi .lastIndextài sản của regex? Mặc dù điều này sẽ chỉ hoạt động cho các mẫu với cờ toàn cầu ( /g).

Chỉnh sửa: Cập nhật để vượt qua các trường hợp thử nghiệm.

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

Điều này có vẻ hứa hẹn nhất cho đến nay (sau một vài sửa chữa sytax) :-) Chỉ thất bại một vài thử nghiệm trên các điều kiện cạnh. Những thứ như 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Tôi đang xem xét liệu tôi có thể khắc phục những trường hợp đó không
Pat

6

Bạn có thể sử dụng chất nền.

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

Từ cuốn sách JavaScript nổi tiếng được xuất bản bởi O'Reilly: "chất nền chưa được chuẩn hóa bởi ECMAScript và do đó không được dùng nữa." Nhưng tôi thích ý tưởng cơ bản đằng sau những gì bạn đang nhận được.
Jason Bunting

1
Đó không phải là vấn đề. Nếu bạn thực sự quan tâm đến nó, thay vào đó, hãy sử dụng String.subopes () - bạn chỉ cần thực hiện phép toán một chút khác nhau. Ngoài ra, JavaScript không nên được chú ý 100% với ngôn ngữ gốc của nó.
Peter Bailey

Đây không phải là vấn đề - nếu bạn chạy mã của mình chống lại việc triển khai không triển khai cơ sở vì họ muốn tuân thủ các tiêu chuẩn ECMAScript, bạn sẽ gặp vấn đề. Cấp, thay thế nó bằng chuỗi con không phải là khó thực hiện, nhưng nó là tốt để nhận thức về điều này.
Jason Bunting

1
Thời điểm bạn gặp vấn đề bạn có một giải pháp rất đơn giản. Tôi nghĩ rằng các ý kiến ​​là hợp lý, nhưng bỏ phiếu xuống là phạm vi.
VoronoiPotato

Bạn có thể vui lòng chỉnh sửa câu trả lời của bạn để cung cấp mã demo hoạt động không?
vsync

5

RexExpcác trường hợp đã có thuộc tính last Index (nếu chúng là toàn cục) và vì vậy những gì tôi đang làm là sao chép biểu thức chính quy, sửa đổi nó một chút cho phù hợp với mục đích của chúng tôi, - đặt execnó trên chuỗi và xem xét lastIndex. Điều này chắc chắn sẽ nhanh hơn việc lặp trên chuỗi. (Bạn có đủ ví dụ về cách đặt cái này lên nguyên mẫu chuỗi, phải không?)

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

Bạn cũng có thể tạo nguyên mẫu cho các hàm trên đối tượng 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

Giải thích nhanh về cách tôi sửa đổi RegExp: Vì indexOftôi chỉ cần đảm bảo rằng cờ toàn cầu được đặt. Đối với lastIndexOftôi, tôi đang sử dụng một cái nhìn tiêu cực để tìm sự xuất hiện cuối cùng trừ khi sự RegExpphù hợp đã ở cuối chuỗi.


4

Nó không tự nhiên, nhưng bạn chắc chắn có thể thêm chức năng này

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

Tôi đã không kiểm tra đầy đủ các phương pháp này, nhưng chúng dường như hoạt động cho đến nay.


Cập nhật để xử lý những trường hợp đó
Peter Bailey

Mỗi khi tôi chấp nhận câu trả lời này, tôi lại tìm thấy một trường hợp mới! Chúng cho kết quả khác nhau! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat

tốt, tất nhiên họ là - str.lastIndexOf sẽ thực hiện kiểu ép buộc trên mẫu - chuyển đổi nó thành một chuỗi. Chuỗi "/ [d] /" chắc chắn không được tìm thấy trong đầu vào, do đó, -1 được trả về là thực sự chính xác.
Peter Bailey

Hiểu rồi. Sau khi đọc thông số kỹ thuật trên String.lastIndexOf () - Tôi chỉ hiểu sai về cách đối số đó hoạt động. Phiên bản mới này sẽ xử lý nó.
Peter Bailey

Một cái gì đó vẫn chưa đúng, nhưng nó đã đến muộn ... Tôi sẽ cố gắng lấy một trường hợp thử nghiệm, và có thể sửa nó vào buổi sáng. Xin lỗi vì những rắc rối cho đến nay.
Pat

2

Sau khi tất cả các giải pháp được đề xuất đều thất bại trong các thử nghiệm của tôi bằng cách này hay cách khác, (chỉnh sửa: một số đã được cập nhật để vượt qua các thử nghiệm sau khi tôi viết bài này) Tôi đã tìm thấy triển khai mozilla cho Array.indexOfArray.lastIndexOf

Tôi đã sử dụng chúng để triển khai phiên bản String.prototype.regexIndexOf và String.prototype.regexLastIndexOf của tôi như sau:

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

Chúng dường như vượt qua các chức năng kiểm tra mà tôi cung cấp trong câu hỏi.

Rõ ràng là chúng chỉ hoạt động nếu biểu thức chính quy khớp với một ký tự nhưng điều đó là đủ cho mục đích của tôi vì tôi sẽ sử dụng nó cho những thứ như ([abc], \ s, \ W, \ D)

Tôi sẽ tiếp tục theo dõi câu hỏi trong trường hợp ai đó cung cấp cách triển khai tốt hơn / nhanh hơn / sạch hơn / chung hơn, hoạt động trên bất kỳ biểu thức chính quy nào.


Wow, đó là một đoạn mã dài. Vui lòng kiểm tra câu trả lời cập nhật của tôi và cung cấp thông tin phản hồi. Cảm ơn.
Jason Bunting

Việc triển khai này nhằm mục đích tương thích tuyệt đối với lastIndexOf trong Firefox và công cụ JavaScript SpiderMonkey, bao gồm cả một số trường hợp có thể được cho là trường hợp cạnh. [...] trong các ứng dụng trong thế giới thực, bạn có thể tính toán với mã ít phức tạp hơn nếu bạn bỏ qua các trường hợp đó.
Pat

Hình thành trang mozilla :-) Tôi vừa lấy đoạn mã quảng cáo thay đổi hai dòng để lại tất cả các trường hợp cạnh. Vì một vài câu trả lời khác đã được cập nhật để vượt qua các bài kiểm tra, tôi sẽ thử điểm chuẩn chúng và chấp nhận hiệu quả nhất. Khi tôi có thời gian để xem xét lại vấn đề.
Pat

Tôi đã cập nhật giải pháp của mình và đánh giá cao bất kỳ phản hồi hoặc những điều khiến nó thất bại. Tôi đã thực hiện một thay đổi để khắc phục sự cố chồng chéo được chỉ ra bởi MizardX (hy vọng!)
Jason Bunting

2

Tôi cần một regexIndexOfhàm cũng cho một mảng, vì vậy tôi đã tự lập trình một hàm. Tuy nhiên tôi nghi ngờ rằng nó được tối ưu hóa, nhưng tôi đoán nó sẽ hoạt động tốt.

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

Trong một số trường hợp đơn giản nhất định, bạn có thể đơn giản hóa tìm kiếm ngược của mình bằng cách sử dụng split.

function regexlast(string,re){
  var tokens=string.split(re);
  return (tokens.length>1)?(string.length-tokens[tokens.length-1].length):null;
}

Điều này có một vài vấn đề nghiêm trọng:

  1. các trận đấu chồng chéo sẽ không xuất hiện
  2. chỉ mục trả về là kết thúc trận đấu chứ không phải bắt đầu (tốt nếu regex của bạn là hằng số)

Nhưng về mặt tươi sáng, nó ít mã hơn. Đối với một biểu thức chính có độ dài không đổi không thể chồng lấp (như /\s\w/tìm ranh giới từ), điều này là đủ tốt.


0

Đối với dữ liệu có kết quả trùng khớp, sử dụng chuỗi.search là nhanh nhất trên các trình duyệt. Nó cắt lại một chuỗi mỗi lần lặp để:

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

Đối với dữ liệu dày đặc tôi đã thực hiện điều này. Nó phức tạp so với phương thức thực thi, nhưng đối với dữ liệu dày đặc, nó nhanh hơn 2-10 lần so với mọi phương pháp khác mà tôi đã thử và nhanh hơn khoảng 100 lần so với giải pháp được chấp nhận. Những điểm chính là:

  1. Nó gọi exec trên regex được truyền vào một lần để xác minh có khớp hay thoát sớm. Tôi làm điều này bằng cách sử dụng (? = Trong một phương thức tương tự, nhưng trên IE kiểm tra bằng exec thì nhanh hơn đáng kể.
  2. Nó xây dựng và lưu trữ một regex đã sửa đổi trong định dạng '(r). (?! ?? r) '
  3. Regex mới được thực thi và kết quả từ người thực thi đó hoặc người thực hiện đầu tiên được trả về;

    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 của các phương thức

Tôi không hiểu mục đích của các bài kiểm tra lên hàng đầu. Các tình huống yêu cầu regex là không thể so sánh với lệnh gọi tới indexOf, mà tôi nghĩ là điểm tạo ra phương thức này ngay từ đầu. Để vượt qua bài kiểm tra, sẽ tốt hơn nếu sử dụng 'xxx + (?! X)', hơn là điều chỉnh cách thức biểu thức chính quy lặp lại.


0

Chỉ số cuối cùng của Jason Bunting không hoạt động. Của tôi không phải là tối ưu, nhưng nó hoạt động.

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

Bạn có thể cung cấp một bài kiểm tra khiến tôi thất bại? Nếu bạn thấy nó không hoạt động, hãy cung cấp một trường hợp thử nghiệm, tại sao chỉ cần nói "nó không hoạt động" và cung cấp một giải pháp không tối ưu tại chỗ?
Jason Bunting

Cậu bé Hoo. Bạn hoàn toàn đúng. Tôi nên đã cung cấp một ví dụ. Thật không may, tôi đã chuyển từ mã này vài tháng trước và không biết trường hợp thất bại là gì. : - /
Eli

tốt, đó là cuộc sống. :)
Jason Bunting

0

Vẫn không có phương thức riêng nào thực hiện nhiệm vụ được yêu cầu.

Đây là mã mà tôi đang sử dụng. Nó bắt chước hành vi của String.prototype.indexOfString.prototype.lastIndexOf phương pháp nhưng họ cũng chấp nhận một RegExp như là đối số tìm kiếm, thêm vào một chuỗi đại diện cho các giá trị để tìm kiếm.

Có, nó khá dài khi một câu trả lời diễn ra khi nó cố gắng tuân theo các tiêu chuẩn hiện tại càng gần càng tốt và tất nhiên có chứa một lượng JSDOC hợp lý bình luận . Tuy nhiên, một khi được thu nhỏ, mã chỉ có 2,27k và một khi được nén để truyền, nó chỉ có 1023 byte.

Hai phương thức mà điều này thêm vào String.prototype(sử dụng Object.defineProperty nếu có) là:

  1. searchOf
  2. searchLastOf

Nó vượt qua tất cả các bài kiểm tra mà OP đã đăng và ngoài ra tôi đã kiểm tra các thói quen khá kỹ lưỡng trong quá trình sử dụng hàng ngày của mình và đã cố gắng đảm bảo rằng chúng hoạt động trên nhiều môi trường, nhưng phản hồi / vấn đề luôn được chào đón.

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

Nếu bạn đang tìm kiếm một tra cứu Last Index rất đơn giản với RegExp và không quan tâm nếu nó bắt chước Last IndexOf đến chi tiết cuối cùng, điều này có thể thu hút sự chú ý của bạn.

Tôi chỉ đơn giản đảo ngược chuỗi và trừ chỉ số xuất hiện đầu tiên từ độ dài - 1. Nó xảy ra để vượt qua bài kiểm tra của tôi, nhưng tôi nghĩ có thể phát sinh vấn đề về hiệu suất với các chuỗi dài.

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

Tôi đã sử dụng String.prototype.match(regex)trả về một mảng chuỗi của tất cả các kết quả tìm thấy của chuỗi đã cho regextrong chuỗi (thông tin chi tiết xem tại đây ):

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
}

Sử dụng các biểu thức chính quy tiêu chuẩn:

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

Chà, vì bạn chỉ đang tìm kiếm để phù hợp với vị trí của một nhân vật , regex có thể là quá mức cần thiết.

Tôi đoán tất cả những gì bạn muốn là, thay vì "tìm đầu tiên của nhân vật này", chỉ cần tìm đầu tiên của những nhân vật này.

Tất nhiên đây là câu trả lời đơn giản, nhưng thực hiện những gì câu hỏi của bạn đặt ra, mặc dù không có phần regex (vì bạn không làm rõ lý do tại sao cụ thể nó phải là một regex)

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

Chỉ là một nhận xét về việc vá khỉ - trong khi tôi nhận thức được vấn đề của nó - bạn nghĩ làm ô nhiễm không gian tên toàn cầu là tốt hơn? Nó không giống như xung đột biểu tượng trong trường hợp CẢ HAI không thể xảy ra và về cơ bản được tái cấu trúc / sửa chữa theo cùng một cách nếu có vấn đề phát sinh.
Peter Bailey

Chà, tôi cần tìm kiếm \ và trong một số trường hợp \ W và hy vọng tôi không phải liệt kê tất cả các khả năng.
Pat

BaileyP: bạn có thể giải quyết vấn đề này mà không gây ô nhiễm không gian tên toàn cầu, ví dụ: xem jQuery chẳng hạn. sử dụng mô hình đó. một đối tượng cho dự án, công cụ của bạn đi vào bên trong nó. Mootools để lại một hương vị xấu trong miệng của tôi.
Kent Fredric

cũng cần lưu ý tôi không bao giờ viết mã như tôi đã viết ở đó. ví dụ đã được đơn giản hóa vì lý do trường hợp sử dụng.
Kent Fredric
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.