จะค้นหาดัชนีของการเกิดขึ้นทั้งหมดของสตริงหนึ่งในอีกสตริงใน JavaScript ได้อย่างไร


106

ฉันพยายามหาตำแหน่งของสตริงที่เกิดขึ้นทั้งหมดในสตริงอื่นโดยไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่

ตัวอย่างเช่นกำหนดสตริง:

ฉันเรียนรู้การเล่นอูคูเลเล่ในเลบานอน

และสตริงการค้นหาleฉันต้องการรับอาร์เรย์:

[2, 25, 27, 33]

สตริงทั้งสองจะเป็นตัวแปรนั่นคือฉันไม่สามารถฮาร์ดโค้ดค่าของมันได้

ฉันคิดว่านี่เป็นงานง่ายสำหรับนิพจน์ทั่วไป แต่หลังจากพยายามดิ้นรนสักพักเพื่อหาสิ่งที่ใช้ได้ผลฉันก็ไม่มีโชค

ฉันพบตัวอย่างวิธีการใช้งานนี้ให้สำเร็จ.indexOf()แต่ต้องมีวิธีที่รัดกุมกว่านี้ใช่ไหม

คำตอบ:


167
var str = "I learned to play the Ukulele in Lebanon."
var regex = /le/gi, result, indices = [];
while ( (result = regex.exec(str)) ) {
    indices.push(result.index);
}

อัปเดต

ฉันไม่สามารถระบุคำถามเดิมที่สตริงการค้นหาต้องเป็นตัวแปร ฉันได้เขียนเวอร์ชันอื่นเพื่อจัดการกับกรณีนี้ที่ใช้indexOfดังนั้นคุณจึงกลับไปที่จุดเริ่มต้น ตามที่ Wrikken ชี้ไว้ในความคิดเห็นในการทำเช่นนี้สำหรับกรณีทั่วไปที่มีนิพจน์ทั่วไปคุณจะต้องหลีกเลี่ยงอักขระ regex พิเศษซึ่ง ณ จุดนี้ฉันคิดว่าโซลูชัน regex กลายเป็นเรื่องน่าปวดหัวมากกว่าที่ควรค่า

function getIndicesOf(searchStr, str, caseSensitive) {
    var searchStrLen = searchStr.length;
    if (searchStrLen == 0) {
        return [];
    }
    var startIndex = 0, index, indices = [];
    if (!caseSensitive) {
        str = str.toLowerCase();
        searchStr = searchStr.toLowerCase();
    }
    while ((index = str.indexOf(searchStr, startIndex)) > -1) {
        indices.push(index);
        startIndex = index + searchStrLen;
    }
    return indices;
}

var indices = getIndicesOf("le", "I learned to play the Ukulele in Lebanon.");

document.getElementById("output").innerHTML = indices + "";
<div id="output"></div>


2
วิธีการจะleเป็นสตริงตัวแปรที่นี่? แม้ว่าจะมีการใช้new Regexp(str);อักขระพิเศษที่มีอันตรายแฝงตัวอยู่$2.50ก็ตาม สิ่งที่ต้องการregex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));จะใกล้ชิดกับ IMHO มากขึ้น ฉันไม่แน่ใจว่า js มีกลไกการหลีกเลี่ยง regex ในตัวหรือไม่
Wrikken

new RegExp(searchStr)จะเป็นวิธีการและใช่ในกรณีทั่วไปคุณจะต้องหลีกเลี่ยงอักขระพิเศษ มันไม่คุ้มค่าที่จะทำเว้นแต่คุณจะต้องการความเป็นธรรมดาระดับนั้น
Tim Down

1
คำตอบที่ดีและเป็นประโยชน์มาก ขอบคุณมากทิม!
Bungle

1
หากสตริงการค้นหาเป็นสตริงว่างคุณจะได้รับการวนซ้ำแบบไม่มีที่สิ้นสุด ... จะทำการตรวจสอบ
HelpMeStackOverflowMyOnlyHope

3
สมมติว่าและว่าsearchStr=aaa str=aaaaaaจากนั้นแทนที่จะค้นหา 4 ที่เกิดขึ้นโค้ดของคุณจะพบเพียง 2 เนื่องจากคุณกำลังข้ามไปsearchStr.lengthในลูป
blazs

19

นี่คือ regex เวอร์ชันฟรี:

function indexes(source, find) {
  if (!source) {
    return [];
  }
  // if find is empty string return all indexes.
  if (!find) {
    // or shorter arrow function:
    // return source.split('').map((_,i) => i);
    return source.split('').map(function(_, i) { return i; });
  }
  var result = [];
  for (i = 0; i < source.length; ++i) {
    // If you want to search case insensitive use 
    // if (source.substring(i, i + find.length).toLowerCase() == find) {
    if (source.substring(i, i + find.length) == find) {
      result.push(i);
    }
  }
  return result;
}

indexes("I learned to play the Ukulele in Lebanon.", "le")

แก้ไข : และหากคุณต้องการจับคู่สตริงเช่น 'aaaa' และ 'aa' เพื่อค้นหา [0, 2] ให้ใช้เวอร์ชันนี้:

function indexes(source, find) {
  if (!source) {
    return [];
  }
  if (!find) {
      return source.split('').map(function(_, i) { return i; });
  }
  var result = [];
  var i = 0;
  while(i < source.length) {
    if (source.substring(i, i + find.length) == find) {
      result.push(i);
      i += find.length;
    } else {
      i++;
    }
  }
  return result;
}

7
+1. ฉันทำการทดสอบเพื่อเปรียบเทียบกับโซลูชันโดยใช้ Regex วิธีที่เร็วที่สุดคือวิธีที่ใช้ Regex: jsperf.com/javascript-find-all
StuR

1
วิธีที่เร็วที่สุดคือใช้ indexOf jsperf.com/find-o-substrings
Ethan Yanjia Li

@LiEthan มันจะสำคัญก็ต่อเมื่อฟังก์ชันนั้นเป็นคอขวดและบางทีถ้าสตริงอินพุตยาว
jcubic

@jcubic วิธีแก้ปัญหาของคุณดูเหมือนดี แต่มีความสับสนเล็กน้อย จะเกิดอะไรขึ้นถ้าฉันเรียกใช้ฟังก์ชันเช่นนี้var result = indexes('aaaa', 'aa')? ผลที่คาดว่าควรจะเป็น[0, 1, 2]หรือ[0, 2]?
Cao Mạnh Quang

@ CaoMạnhQuangดูโค้ดเป็นผลลัพธ์แรก หากคุณต้องการอันที่สองคุณต้องสร้างในขณะที่วนซ้ำและภายในถ้าคุณใส่i+=find.length;และอื่น ๆi++
jcubic

15

คุณทำได้แน่!

//make a regular expression out of your needle
var needle = 'le'
var re = new RegExp(needle,'gi');
var haystack = 'I learned to play the Ukulele';

var results = new Array();//this is the results you want
while (re.exec(haystack)){
  results.push(re.lastIndex);
}

แก้ไข: เรียนรู้การสะกด RegExp

นอกจากนี้ผมตระหนักในเรื่องนี้ไม่ว่าสิ่งที่คุณต้องการเช่นlastIndexบอกเราในตอนท้ายของเข็มไม่ได้เริ่มต้น แต่มันใกล้ - คุณสามารถผลักดันre.lastIndex-needle.lengthเข้าสู่อาร์เรย์ผล ...

แก้ไข: กำลังเพิ่มลิงค์

คำตอบของ @Tim Down ใช้ออบเจ็กต์ผลลัพธ์จาก RegExp.exec () และทรัพยากร Javascript ทั้งหมดของฉันจะเน้นการใช้งาน (นอกเหนือจากการให้สตริงที่ตรงกันแก่คุณ) ดังนั้นเมื่อเขาใช้result.indexนั่นคือ Match Object ที่ไม่มีชื่อ ในคำอธิบาย MDC ของ execพวกเขาอธิบายถึงวัตถุนี้อย่างละเอียด


ฮา! ขอขอบคุณที่ร่วมให้ข้อมูลไม่ว่าในกรณีใด ๆ - ขอขอบคุณ!
Bungle

10

ซับหนึ่งโดยใช้String.protype.matchAll(ES2020):

[...sourceStr.matchAll(new RegExp(searchStr, 'gi'))].map(a => a.index)

ใช้ค่าของคุณ:

const sourceStr = 'I learned to play the Ukulele in Lebanon.';
const searchStr = 'le';
const indexes = [...sourceStr.matchAll(new RegExp(searchStr, 'gi'))].map(a => a.index);
console.log(indexes); // [2, 25, 27, 33]

หากคุณกังวลเกี่ยวกับการแพร่กระจายและการแพร่กระจายmap()ในบรรทัดเดียวฉันจะเรียกใช้มันด้วยการfor...ofวนซ้ำสำหรับการวนซ้ำเป็นล้านครั้ง (โดยใช้สตริงของคุณ) ซับหนึ่งเฉลี่ย 1420ms ในขณะที่for...ofเฉลี่ย 1150ms บนเครื่องของฉัน นั่นไม่ใช่ความแตกต่างเล็กน้อย แต่ซับหนึ่งจะทำงานได้ดีหากคุณทำการแข่งขันเพียงไม่กี่ครั้ง

ดูmatchAllใน caniuse


4

หากคุณต้องการหาตำแหน่งของการแข่งขันทั้งหมดฉันต้องการชี้ให้คุณเห็นการแฮ็กเล็กน้อย:

var haystack = 'I learned to play the Ukulele in Lebanon.',
    needle = 'le',
    splitOnFound = haystack.split(needle).map(function (culm)
    {
        return this.pos += culm.length + needle.length
    }, {pos: -needle.length}).slice(0, -1); // {pos: ...} – Object wich is used as this

console.log(splitOnFound);

อาจไม่สามารถใช้งานได้หากคุณมี RegExp ที่มีความยาวผันแปรได้ แต่สำหรับบางคนอาจเป็นประโยชน์

นี่เป็นกรณีที่ละเอียดอ่อน สำหรับString.toLowerCaseฟังก์ชั่นการใช้งานกรณีไม่ไวก่อน


ฉันคิดว่าคำตอบของคุณเป็นคำตอบที่ดีที่สุดเนื่องจากการใช้ RegExp นั้นอันตราย
Bharata

1

นี่คือตัวอย่างโค้ดง่ายๆ:

function getIndexOfSubStr(str, searchToken, preIndex, output) {
    var result = str.match(searchToken);
    if (result) {
        output.push(result.index +preIndex);
        str=str.substring(result.index+searchToken.length);
        getIndexOfSubStr(str, searchToken, preIndex, output)
    }
    return output;
}

var str = "my name is 'xyz' and my school name is 'xyz' and my area name is 'xyz' ";
var searchToken ="my";
var preIndex = 0;

console.log(getIndexOfSubStr(str, searchToken, preIndex, []));


0

ขอบคุณสำหรับการตอบกลับทั้งหมด ฉันอ่านข้อมูลทั้งหมดและได้ฟังก์ชันที่ให้ดัชนีสุดท้ายแรกของการเกิดสตริงย่อย 'เข็ม' แต่ละครั้ง ฉันโพสต์ไว้ที่นี่เผื่อว่าจะช่วยใครได้

โปรดทราบว่ามันไม่เหมือนกับคำขอเดิมสำหรับจุดเริ่มต้นของแต่ละเหตุการณ์เท่านั้น เหมาะกับการใช้งานของฉันดีกว่าเพราะคุณไม่จำเป็นต้องรักษาความยาวของเข็ม

function findRegexIndices(text, needle, caseSensitive){
  var needleLen = needle.length,
    reg = new RegExp(needle, caseSensitive ? 'gi' : 'g'),
    indices = [],
    result;

  while ( (result = reg.exec(text)) ) {
    indices.push([result.index, result.index + needleLen]);
  }
  return indices
}

0

ตรวจสอบโซลูชันนี้ซึ่งจะสามารถค้นหาสตริงอักขระเดียวกันได้เช่นกันแจ้งให้เราทราบหากมีบางอย่างหายไปหรือไม่ถูกต้อง

function indexes(source, find) {
    if (!source) {
      return [];
    }
    if (!find) {
        return source.split('').map(function(_, i) { return i; });
    }
    source = source.toLowerCase();
    find = find.toLowerCase();
    var result = [];
    var i = 0;
    while(i < source.length) {
      if (source.substring(i, i + find.length) == find)
        result.push(i++);
      else
        i++
    }
    return result;
  }
  console.log(indexes('aaaaaaaa', 'aaaaaa'))
  console.log(indexes('aeeaaaaadjfhfnaaaaadjddjaa', 'aaaa'))
  console.log(indexes('wordgoodwordgoodgoodbestword', 'wordgood'))
  console.log(indexes('I learned to play the Ukulele in Lebanon.', 'le'))


0

ฉันไปงานปาร์ตี้ช้าไปหน่อย (เกือบ 10 ปี 2 เดือน) แต่วิธีหนึ่งสำหรับผู้เขียนโค้ดในอนาคตคือใช้ในขณะที่วนซ้ำและ indexOf()

let haystack = "I learned to play the Ukulele in Lebanon.";
let needle = "le";
let pos = 0; // Position Ref
let result = []; // Final output of all index's.
let hayStackLower = haystack.toLowerCase();

// Loop to check all occurrences 
while (hayStackLower.indexOf(needle, pos) != -1) {
  result.push(hayStackLower.indexOf(needle , pos));
  pos = hayStackLower.indexOf(needle , pos) + 1;
}

console.log("Final ", result); // Returns all indexes or empty array if not found

0

ทำตามคำตอบของ @jcubic วิธีแก้ปัญหาของเขาทำให้เกิดความสับสนเล็กน้อยสำหรับกรณีของฉัน
ตัวอย่างเช่นvar result = indexes('aaaa', 'aa')จะกลับมา[0, 1, 2]แทนที่จะเป็น[0, 2]
ดังนั้นฉันจึงอัปเดตโซลูชันของเขาเล็กน้อยด้านล่างเพื่อให้ตรงกับกรณีของฉัน

function indexes(text, subText, caseSensitive) {
    var _source = text;
    var _find = subText;
    if (caseSensitive != true) {
        _source = _source.toLowerCase();
        _find = _find.toLowerCase();
    }
    var result = [];
    for (var i = 0; i < _source.length;) {
        if (_source.substring(i, i + _find.length) == _find) {
            result.push(i);
            i += _find.length;  // found a subText, skip to next position
        } else {
            i += 1;
        }
    }
    return result;
}

0

ฉันขอแนะนำคำตอบของทิม อย่างไรก็ตามความคิดเห็นนี้โดย @blazs ระบุว่า "สมมติว่า searchStr = aaa และ str = aaaaaa จากนั้นแทนที่จะค้นหา 4 ที่เกิดขึ้นโค้ดของคุณจะพบเพียง 2 เพราะคุณทำการข้ามโดย searchStr.length ในลูป" ซึ่งเป็นความจริง โดยดูรหัสของ Tim โดยเฉพาะบรรทัดนี้ที่นี่: startIndex = index + searchStrLen;รหัสของ Tim จะไม่สามารถค้นหาอินสแตนซ์ของสตริงที่กำลังค้นหาซึ่งอยู่ในความยาวของตัวมันเอง ดังนั้นฉันจึงแก้ไขคำตอบของ Tim:

function getIndicesOf(searchStr, str, caseSensitive) {
    var startIndex = 0, index, indices = [];
    if (!caseSensitive) {
        str = str.toLowerCase();
        searchStr = searchStr.toLowerCase();
    }
    while ((index = str.indexOf(searchStr, startIndex)) > -1) {
        indices.push(index);
        startIndex = index + 1;
    }
    return indices;
}

var indices = getIndicesOf("le", "I learned to play the Ukulele in Lebanon.");

document.getElementById("output").innerHTML = indices + "";
<div id="output"></div>

การเปลี่ยนเป็น+ 1แทนที่จะ+ searchStrLenอนุญาตให้ดัชนี 1 อยู่ในอาร์เรย์ดัชนีถ้าฉันมี str aaaaและ searchStr ของaa .

คำตอบที่สอง

ฉันมีข้อมูลโค้ดอื่นที่ใช้งานได้:

function getIndicesOf(searchStr, str, caseSensitive) {
    var startIndex = 0, index, indices = [];
    if (!caseSensitive) {
        str = str.toLowerCase();
        searchStr = searchStr.toLowerCase();
    }
    for (var i=0; i<str.length-1; i++) {
        if (str.substr(i, searchStr.length) == searchStr) {
            indices.push(i);
        }
    }
    return indices;
}

var indices = getIndicesOf("le", "I learned to play the Ukulele in Lebanon.");

document.getElementById("output").innerHTML = indices + "";
<div id="output"></div>

อย่างไรก็ตามข้อเสียของตัวอย่างข้อมูลนี้คืออาจใช้เวลานานกว่าอันแรกเล็กน้อยเนื่องจากอันแรกใช้ฟังก์ชันในตัวของ JavaScript ในindexOf()ขณะที่อันที่สองนั้นคล้ายกับคำพูดเดิม ๆ ว่า "การประดิษฐ์ ล้อ." ดังนั้นโดยรวมแล้วฉันขอแนะนำคำตอบแรกแทนคำตอบนี้ ป.ล. หากใครต้องการความคิดเห็นในโค้ดเพื่ออธิบายการทำงานของโค้ดก็บอกมาได้เลยเรายินดีที่จะตอบกลับคำขอ


-1
function countInString(searchFor,searchIn){

 var results=0;
 var a=searchIn.indexOf(searchFor)

 while(a!=-1){
   searchIn=searchIn.slice(a*1+searchFor.length);
   results++;
   a=searchIn.indexOf(searchFor);
 }

return results;

}

สิ่งนี้ค้นหาการเกิดขึ้นของสตริงภายในสตริงอื่นแทนที่จะเป็นนิพจน์ทั่วไป

-1

รหัสด้านล่างจะทำงานให้คุณ:

function indexes(source, find) {
  var result = [];
  for(i=0;i<str.length; ++i) {
    // If you want to search case insensitive use 
    // if (source.substring(i, i + find.length).toLowerCase() == find) {
    if (source.substring(i, i + find.length) == find) {
      result.push(i);
    }
  }
  return result;
}

indexes("hello, how are you", "ar")

-2

ใช้String.prototype.match

นี่คือตัวอย่างจากเอกสาร MDN เอง:

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var regexp = /[A-E]/gi;
var matches_array = str.match(regexp);

console.log(matches_array);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']

12
คำถามคือจะหาดัชนีของเหตุการณ์ที่เกิดขึ้นอย่างไรไม่ให้เกิดขึ้นเอง!
Luckylooke
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.