Apakah ada versi String.indexOf () JavaScript yang memungkinkan untuk ekspresi reguler?

214

Dalam javascript, apakah ada yang setara dengan String.indexOf () yang mengambil ekspresi reguler alih-alih string untuk parameter pertama pertama sementara masih mengizinkan parameter kedua?

Saya perlu melakukan sesuatu seperti

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

dan

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

Meskipun String.search () menggunakan regexp sebagai parameter, itu tidak memungkinkan saya untuk menentukan argumen kedua!

Sunting:
Ini ternyata lebih sulit daripada yang saya pikir awalnya jadi saya menulis fungsi uji kecil untuk menguji semua solusi yang disediakan ... ia menganggap regexIndexOf dan regexLastIndexOf telah ditambahkan ke objek 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)]) ;
    }
}

dan saya menguji sebagai berikut untuk memastikan bahwa setidaknya untuk satu karakter regexp, hasilnya sama seperti jika kita menggunakan indexOf

// Cari a diantara
tes xes ('xxx');
test ('axx');
test ('xax');
test ('xxa');
test ('axa');
test ('xaa');
test ('aax');
test ('aaa');

Menepuk
sumber
|di dalam [ ]cocok dengan karakter literal |. Anda mungkin bermaksud [abc].
Markus Jarderot
ya terima kasih Anda benar, saya akan memperbaikinya tetapi regexp itu sendiri tidak relevan ...
Pat
Memperbarui jawaban saya Pat, terima kasih atas umpan baliknya.
Jason Bunting
Saya menemukan pendekatan yang lebih sederhana dan efektif adalah dengan hanya menggunakan string.match (/ [AZ] /). Jika tidak ada banyak, metode mengembalikan nol, jika tidak Anda mendapatkan objek, Anda dapat melakukan pencocokan (/ [AZ] /). Indeks untuk mendapatkan indeks huruf kapital pertama
Syler

Jawaban:

129

Menggabungkan beberapa pendekatan yang telah disebutkan (indexOf jelas agak sederhana), saya pikir ini adalah fungsi yang akan melakukan trik:

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

Jelas, memodifikasi objek String bawaan akan mengirimkan bendera merah untuk kebanyakan orang, tetapi ini mungkin satu kali ketika itu bukan masalah besar; cukup sadari itu.


UPDATE: Diedit regexLastIndexOf()sehingga sepertinya meniru lastIndexOf()sekarang. Tolong beri tahu saya jika masih gagal dan dalam keadaan apa.


PEMBARUAN: Lulus semua tes yang ditemukan di dalam komentar di halaman ini, dan milik saya. Tentu saja, itu tidak berarti itu anti peluru. Setiap umpan balik dihargai.

Jason Bunting
sumber
Anda regexLastIndexOfhanya akan mengembalikan indeks pertandingan yang tidak tumpang tindih terakhir.
Markus Jarderot
Maaf, bukan pria regex BESAR - dapatkah Anda memberi saya contoh yang akan membuat saya gagal? Saya menghargai bisa belajar lebih banyak, tetapi tanggapan Anda tidak membantu seseorang yang sama bodohnya dengan saya. :)
Jason Bunting
Jason Saya baru saja menambahkan beberapa fungsi untuk menguji dalam pertanyaan. ini gagal (di antara tes lain) 'axx'.lastIndexOf (' a ', 2) berikut ini! =' axx'.regexLastIndexOf (/ a /, 2)
Pat
2
Saya pikir ini lebih efisien untuk digunakan regex.lastIndex = result.index + 1;daripada regex.lastIndex = ++nextStop;. Ini akan berlanjut ke pertandingan berikutnya jauh lebih cepat semoga tanpa kehilangan hasil apa pun.
Gedrox
1
Jika Anda lebih suka menariknya dari npm, kedua fungsi util ini sekarang berada di NPM sebagai: npmjs.com/package/index-of-regex
Capaj
185

Contoh Stringkonstruktor memiliki .search()metode yang menerima RegExp dan mengembalikan indeks pertandingan pertama.

Untuk memulai pencarian dari posisi tertentu (memalsukan parameter kedua .indexOf()), Anda dapat slicemematikan ikarakter pertama :

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

Tapi ini akan mendapatkan indeks dalam string yang lebih pendek (setelah bagian pertama dipotong) sehingga Anda ingin kemudian menambahkan panjang bagian yang dipotong ( i) ke indeks yang dikembalikan jika tidak -1. Ini akan memberi Anda indeks dalam string asli:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}
Glenn
sumber
1
dari pertanyaan: Sementara String.search () mengambil regexp sebagai parameter, itu tidak memungkinkan saya untuk menentukan argumen kedua!
Pat
14
str.substr (i) .search (/ re /)
Glenn
6
Solusi hebat, namun outputnya sedikit berbeda. indexOf akan mengembalikan angka dari awal (terlepas dari offset), sedangkan ini akan mengembalikan posisi dari offset. Jadi, untuk paritas, Anda akan menginginkan sesuatu yang lebih seperti ini:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger
39

Saya punya versi singkat untuk Anda. Ini bekerja dengan baik untuk saya!

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

Dan jika Anda menginginkan versi prototipe:

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

Sunting : jika Anda ingin menambahkan dukungan untuk dariIndex

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

Untuk menggunakannya, sesederhana ini:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);
pmrotule
sumber
Ini sebenarnya trik yang bagus. Akan lebih bagus jika Anda memperluasnya untuk juga mengambil startIndexparameter seperti biasa indeoxOfdan lastIndexOflakukan.
Robert Koritnik
@RobertKoritnik - Saya mengedit jawaban saya untuk mendukung startIndex(atau fromIndex). Semoga ini bisa membantu!
pmrotule
lastIndexOfRegexjuga harus menambahkan kembali nilai fromIndexke hasil.
Peter
Algoritme Anda akan bubar dalam skenario berikut: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Hasilnya akan menjadi 1 ketika seharusnya menjadi 7, karena indexOf akan mencari pertama kali "romeo" muncul, tidak peduli apakah itu di awal kata atau tidak.
KorelK
13

Menggunakan:

str.search(regex)

Lihat dokumentasi di sini.

rmg.n3t
sumber
11
@ OZZIE: Tidak, tidak juga. Ini pada dasarnya jawaban Glenn (dengan ~ 150 upvotes), kecuali ia tidak memiliki penjelasan apa pun, tidak mendukung posisi awal selain 0, dan telah diposting ... tujuh tahun kemudian.
ccjmne
7

Berdasarkan jawaban BaileyP. Perbedaan utama adalah bahwa metode ini kembali -1jika polanya tidak cocok.

Sunting: Terima kasih atas jawaban Jason Bunting, saya mendapat ide. Mengapa tidak memodifikasi .lastIndexproperti regex? Padahal ini hanya akan berfungsi untuk pola dengan bendera global ( /g).

Sunting: Diperbarui untuk lulus test case.

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;
}
Markus Jarderot
sumber
Ini tampaknya yang paling menjanjikan sejauh ini (setelah beberapa perbaikan sytax) :-) Hanya gagal beberapa tes pada kondisi tepi. Hal-hal seperti 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Saya mencari ke dalamnya untuk melihat apakah saya dapat memperbaiki kasus-kasus tersebut
Pat
6

Anda bisa menggunakan substr.

str.substr(i).match(/[abc]/);
Andru Luvisi
sumber
Dari buku JavaScript terkenal yang diterbitkan oleh O'Reilly: "substr belum distandarisasi oleh ECMAScript dan karena itu sudah usang." Tapi saya suka ide dasar di balik apa yang Anda maksudkan.
Jason Bunting
1
Itu bukan masalah. Jika Anda BENAR-BENAR khawatir tentang hal itu, gunakan String.substring () - Anda hanya perlu melakukan matematika sedikit berbeda. Selain itu, JavaScript tidak boleh 100% terikat pada bahasa induknya.
Peter Bailey
Ini bukan masalah - jika Anda menjalankan kode terhadap implementasi yang tidak mengimplementasikan substr karena mereka ingin mematuhi standar ECMAScript, Anda akan memiliki masalah. Memang, menggantinya dengan substring tidak begitu sulit untuk dilakukan, tetapi baik untuk menyadari hal ini.
Jason Bunting
1
Saat Anda memiliki masalah, Anda memiliki solusi yang sangat sangat sederhana. Saya pikir komentarnya masuk akal, tetapi suara turun itu pedantic.
VoronoiPotato
Bisakah Anda mengedit jawaban Anda untuk memberikan kode demo yang berfungsi?
vsync
5

RexExpcontoh memiliki lastIndex properti sudah (jika mereka global) dan apa yang saya lakukan adalah menyalin ekspresi reguler, memodifikasi sedikit untuk memenuhi tujuan kita, exec-ing pada string dan melihat lastIndex. Ini pasti akan lebih cepat daripada mengulang pada string. (Anda punya cukup banyak contoh cara memasukkan ini ke prototipe string, kan?)

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

Anda juga bisa membuat prototipe fungsi ke objek 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

Penjelasan singkat tentang bagaimana saya memodifikasi RegExp: Karena indexOfsaya hanya perlu memastikan bahwa bendera global diatur. UntuklastIndexOf saya menggunakan pandangan negatif ke depan untuk menemukan kejadian terakhir kecuali RegExpsudah cocok pada akhir string.

Prestaul
sumber
4

Ini tidak asli, tetapi Anda tentu dapat menambahkan fungsi ini

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

Saya tidak sepenuhnya menguji metode ini, tetapi mereka tampaknya bekerja sejauh ini.

Peter Bailey
sumber
Diperbarui untuk menangani kasus-kasus tersebut
Peter Bailey
setiap kali saya akan menerima jawaban ini saya menemukan kasus baru! Ini memberikan hasil yang berbeda! lansiran ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]));
Pat
baik, tentu saja mereka - str.lastIndexOf akan melakukan paksaan pada pola - mengubahnya menjadi string. String "/ [d] /" pastinya tidak ditemukan dalam input, sehingga -1 yang dikembalikan sebenarnya akurat.
Peter Bailey
Mengerti. Setelah membaca spesifikasi pada String.lastIndexOf () - Saya hanya salah mengerti bagaimana argumen itu bekerja. Versi baru ini harus menanganinya.
Peter Bailey
Ada yang masih tidak beres, tapi sudah terlambat ... Saya akan mencoba untuk mendapatkan test case, dan mungkin memperbaikinya di pagi hari. Maaf atas masalahnya sejauh ini.
Pat
2

Setelah semua solusi yang diajukan gagal dalam pengujian saya dengan satu atau lain cara, (sunting: ada yang diperbarui untuk lulus tes setelah saya menulis ini) Saya menemukan implementasi mozilla untuk Array.indexOf dan Array.lastIndexOf

Saya menggunakan itu untuk mengimplementasikan versi saya dari String.prototype.regexIndexOf dan String.prototype.regexLastIndexOf sebagai berikut:

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

Mereka tampaknya lulus fungsi tes yang saya berikan dalam pertanyaan.

Jelas mereka hanya berfungsi jika ekspresi reguler cocok dengan satu karakter tetapi itu sudah cukup untuk tujuan saya karena saya akan menggunakannya untuk hal-hal seperti ([abc], \ s, \ W, \ D)

Saya akan terus memantau pertanyaan jika seseorang menyediakan implementasi generik yang lebih baik / lebih cepat / lebih bersih / lebih umum yang berfungsi pada ekspresi reguler apa pun.

Menepuk
sumber
Wow, itu kode yang panjang. Silakan periksa jawaban saya yang diperbarui dan berikan umpan balik. Terima kasih.
Jason Bunting
Implementasi ini bertujuan untuk kompatibilitas absolut dengan lastIndexOf di Firefox dan mesin JavaScript SpiderMonkey, termasuk dalam beberapa kasus yang bisa dibilang kasus tepi. [...] dalam aplikasi dunia nyata, Anda mungkin dapat menghitung dari dengan kode yang tidak terlalu rumit jika Anda mengabaikan kasus-kasus itu.
Pat
Bentuk halaman mozilla :-) Saya baru saja mengambil kode iklan mengubah dua baris meninggalkan semua tepi kasus. Karena beberapa jawaban lain diperbarui untuk lulus tes, saya akan mencoba membuat tolok ukur dan menerima yang paling efisien. Ketika saya punya waktu untuk meninjau kembali masalah ini.
Pat
Saya memperbarui solusi saya dan menghargai setiap umpan balik atau hal-hal yang menyebabkannya gagal. Saya membuat perubahan untuk memperbaiki masalah yang tumpang tindih yang ditunjukkan oleh MizardX (semoga!)
Jason Bunting
2

Saya membutuhkan regexIndexOffungsi juga untuk sebuah array, jadi saya memprogramnya sendiri. Namun saya ragu, itu dioptimalkan, tetapi saya kira itu harus bekerja dengan baik.

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);
Jakov
sumber
1

Dalam kasus-kasus sederhana tertentu, Anda dapat menyederhanakan pencarian mundur Anda dengan menggunakan split.

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

Ini memiliki beberapa masalah serius:

  1. pertandingan yang tumpang tindih tidak akan muncul
  2. indeks yang dikembalikan adalah untuk akhir pertandingan dan bukan awal (baik jika regex Anda adalah konstan)

Tapi di sisi baiknya, kode itu jauh lebih sedikit. Untuk regex dengan panjang konstan yang tidak dapat tumpang tindih (seperti /\s\w/untuk menemukan batas kata) ini cukup baik.

amwinter
sumber
0

Untuk data dengan kecocokan yang jarang, menggunakan string.search adalah yang tercepat di seluruh browser. Ini mengiris kembali string setiap iterasi ke:

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

Untuk data yang padat saya membuat ini. Ini kompleks dibandingkan dengan metode eksekusi, tetapi untuk data yang padat, ini 2-10x lebih cepat daripada setiap metode lain yang saya coba, dan sekitar 100x lebih cepat dari solusi yang diterima. Poin utamanya adalah:

  1. Itu memanggil exec pada regex yang dilewati satu kali untuk memverifikasi ada kecocokan atau berhenti lebih awal. Saya melakukan ini menggunakan (? = Dalam metode yang serupa, tetapi pada IE memeriksa dengan exec secara dramatis lebih cepat.
  2. Itu membangun dan cache regex yang dimodifikasi dalam format '(r). (?!. ? r) '
  3. Regex baru dijalankan dan hasil dari eksekutif tersebut, atau eksekutif pertama, dikembalikan;

    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 metode

Saya tidak mengerti tujuan dari tes di bagian atas. Situasi yang memerlukan regex tidak mungkin dibandingkan dengan panggilan ke indexOf, yang saya pikir adalah titik membuat metode di tempat pertama. Untuk mendapatkan lulus ujian, lebih masuk akal untuk menggunakan 'xxx + (?! x)', daripada menyesuaikan cara regex iterates.

npjohns
sumber
0

Indeks terakhir Jason Bunting tidak berfungsi. Milik saya tidak optimal, tetapi berhasil.

//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;
}
Eli
sumber
Bisakah Anda memberikan tes yang menyebabkan tes saya gagal? Jika ternyata tidak berfungsi, sediakan uji kasus, mengapa hanya mengatakan "tidak berfungsi" dan berikan solusi yang tidak optimal?
Jason Bunting
Hoo boy. Anda sepenuhnya benar. Saya seharusnya memberikan contoh. Sayangnya saya pindah dari kode ini berbulan-bulan yang lalu dan tidak tahu apa kasus kegagalan itu. : - /
Eli
yah, begitulah hidup. :)
Jason Bunting
0

Masih belum ada metode asli yang melakukan tugas yang diminta.

Berikut adalah kode yang saya gunakan. Ini meniru perilaku String.prototype.indexOf dan String.prototype.lastIndexOf metode tetapi mereka juga menerima regexp sebagai argumen pencarian selain string yang mewakili nilai untuk mencari.

Ya itu cukup panjang karena jawabannya berusaha untuk mengikuti standar saat ini sedekat mungkin dan tentu saja mengandung jumlah JSDOC yang masuk akal komentar . Namun, setelah diperkecil, kodenya hanya 2.27k dan sekali di-gzip untuk transmisi hanya 1023 byte.

2 metode yang ditambahkan ini String.prototype(menggunakan Object.defineProperty jika tersedia) adalah:

  1. searchOf
  2. searchLastOf

Ini melewati semua tes yang diposting OP dan juga saya telah menguji rutinitas cukup menyeluruh dalam penggunaan sehari-hari saya, dan telah berusaha untuk memastikan bahwa mereka bekerja di berbagai lingkungan, tetapi umpan balik / masalah selalu diterima.

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

Xotic750
sumber
0

Jika Anda mencari lastIndex lookup yang sangat sederhana dengan RegExp dan tidak peduli apakah itu meniru lastIndexOf ke detail terakhir, ini mungkin menarik perhatian Anda.

Saya hanya membalikkan string, dan mengurangi indeks kemunculan pertama dari panjang - 1. Kebetulan lulus tes saya, tapi saya pikir mungkin ada masalah kinerja dengan string panjang.

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;
};
Reijo
sumber
0

Saya menggunakan String.prototype.match(regex)yang mengembalikan array string dari semua kecocokan yang ditemukan dari yang diberikan regexdalam string (info lebih lanjut lihat di sini ):

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

wfreude
sumber
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
}

Gunakan persamaan reguler standar:

var re  = new RegExp("^ab");  // At front
var re  = new RegExp("ab$");  // At end
var re  = new RegExp("ab(c|d)");  // abc or abd
pengguna984003
sumber
-2

Yah, karena Anda hanya ingin mencocokkan posisi karakter , regex mungkin berlebihan.

Saya kira semua yang Anda inginkan adalah, alih-alih "temukan dulu karakter ini", cari saja dulu karakter ini.

Ini tentu saja adalah jawaban yang sederhana, tetapi melakukan apa yang ingin dilakukan pertanyaan Anda, meskipun tanpa bagian regex (karena Anda tidak menjelaskan mengapa khusus itu harus menjadi sebuah 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
Kent Fredric
sumber
Hanya komentar tentang tambalan monyet - sementara saya menyadari masalahnya - Anda pikir mencemari namespace global lebih baik? Ini tidak seperti konflik simbol dalam KEDUA kasus yang tidak dapat terjadi, dan pada dasarnya dire-refored / diperbaiki dengan cara yang sama seandainya muncul masalah.
Peter Bailey
Yah saya perlu mencari \ s dan dalam beberapa kasus \ W dan berharap saya tidak perlu menyebutkan semua kemungkinan.
Pat
BaileyP: Anda dapat mengatasi masalah ini tanpa polusi namespace global, yaitu: lihat jQuery misalnya. gunakan model itu. satu objek untuk proyek, barang-barang Anda masuk ke dalamnya. Mootool meninggalkan rasa tidak enak di mulut saya.
Kent Fredric
juga untuk dicatat saya tidak pernah kode seperti yang saya tulis di sana. contoh disederhanakan karena alasan penggunaan.
Kent Fredric