Làm cách nào để tìm chỉ số của tất cả các lần xuất hiện của một chuỗi này trong chuỗi khác trong JavaScript?


104

Tôi đang cố gắng tìm vị trí của tất cả các lần xuất hiện của một chuỗi trong một chuỗi khác, không phân biệt chữ hoa chữ thường.

Ví dụ, cho chuỗi:

Tôi đã học chơi đàn Ukulele ở Lebanon.

và chuỗi tìm kiếm le, tôi muốn lấy mảng:

[2, 25, 27, 33]

Cả hai chuỗi sẽ là các biến - tức là, tôi không thể mã hóa các giá trị của chúng.

Tôi nhận ra rằng đây là một nhiệm vụ dễ dàng đối với biểu thức chính quy, nhưng sau một thời gian vật lộn để tìm một cái có thể hoạt động, tôi đã không gặp may.

Tôi đã tìm thấy ví dụ này về cách thực hiện điều này bằng cách sử dụng .indexOf(), nhưng chắc chắn phải có một cách ngắn gọn hơn để làm điều đó?

Câu trả lời:


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

CẬP NHẬT

Tôi đã không phát hiện ra trong câu hỏi ban đầu rằng chuỗi tìm kiếm cần phải là một biến. Tôi đã viết một phiên bản khác để giải quyết trường hợp này sử dụng indexOf, vì vậy bạn quay lại nơi bạn bắt đầu. Như đã chỉ ra bởi Wrikken trong các nhận xét, để làm điều này đối với trường hợp chung với biểu thức chính quy, bạn sẽ cần phải thoát các ký tự regex đặc biệt, tại thời điểm đó tôi nghĩ rằng giải pháp regex trở nên đau đầu hơn so với giá trị của nó.

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
Làm thế nào sẽ lelà một chuỗi biến ở đây? Ngay cả khi sử dụng new Regexp(str);các ký tự đặc biệt nguy hiểm đang rình rập, tìm kiếm $2.50chẳng hạn. Một cái gì đó giống như regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));IMHO gần gũi hơn. Tôi không chắc liệu js có cơ chế thoát regex tích hợp sẵn hay không.
Wrikken

new RegExp(searchStr)sẽ là cách, và vâng, trong trường hợp chung, bạn sẽ phải thoát các ký tự đặc biệt. Nó không thực sự đáng làm trừ khi bạn cần mức độ tổng quát đó.
Tim Down

1
Câu trả lời tuyệt vời và rất hữu ích. Cảm ơn rất nhiều, Tim!
Bungle

1
Nếu chuỗi tìm kiếm là một chuỗi trống, bạn nhận được một vòng lặp vô hạn ... sẽ kiểm tra nó.
HelpMeStackOverflowMyOnlyHope

2
Giả sử searchStr=aaavà điều đó str=aaaaaa. Sau đó, thay vì tìm 4 lần xuất hiện, mã của bạn sẽ chỉ tìm thấy 2 vì bạn đang bỏ qua searchStr.lengthtrong vòng lặp.
blazs

18

Đây là phiên bản miễn phí của 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")

CHỈNH SỬA : và nếu bạn muốn khớp các chuỗi như 'aaaa' và 'aa' để tìm [0, 2], hãy sử dụng phiên bản này:

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. Tôi đã chạy một số thử nghiệm để so sánh với giải pháp sử dụng Regex. Phương pháp nhanh nhất là phương pháp sử dụng Regex: jsperf.com/javascript-find-all
StuR

1
Phương pháp nhanh nhất là sử dụng indexOf jsperf.com/find-o-substrings
Ethan Yanjia Li

@LiEthan sẽ chỉ có vấn đề nếu hàm đó bị tắc nghẽn và có thể nếu chuỗi đầu vào dài.
jcubic

@jcubic Giải pháp của bạn có vẻ tốt, nhưng chỉ có một sự nhầm lẫn nhỏ. Nếu tôi gọi hàm như thế này thì var result = indexes('aaaa', 'aa')sao? Kết quả mong đợi nên [0, 1, 2]hay [0, 2]?
Cao Mạnh Quang

@ CaoMạnhQuang nhìn mã là kết quả đầu tiên. Nếu bạn muốn một thứ hai bạn cần phải tạo ra vòng lặp while và bên trong nếu bạn đặt i+=find.length;và trong kháci++
jcubic

15

Bạn chắc chắn có thể làm điều này!

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

Chỉnh sửa: học cách đánh vần RegExp

Ngoài ra, tôi nhận ra rằng đây không phải là chính xác những gì bạn muốn, như lastIndexcho chúng ta biết điểm cuối của cây kim không phải là điểm bắt đầu, mà là gần - bạn có thể đẩy re.lastIndex-needle.lengthvào mảng kết quả ...

Chỉnh sửa: thêm liên kết

Câu trả lời của @Tim Down sử dụng đối tượng kết quả từ RegExp.exec () và tất cả các tài nguyên Javascript của tôi đều bóng bẩy về việc sử dụng nó (ngoài việc cung cấp cho bạn chuỗi phù hợp). Vì vậy, khi anh ta sử dụng result.index, đó là một số loại Đối tượng phù hợp không tên. Trong mô tả MDC của executive , họ thực sự mô tả đối tượng này khá chi tiết.


Ha! Cảm ơn bạn đã đóng góp, trong mọi trường hợp - tôi đánh giá cao điều đó!
Bungle

8

Một lớp lót sử dụng String.protype.matchAll(ES2020):

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

Sử dụng các giá trị của bạn:

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]

Nếu bạn lo lắng về việc thực hiện một spread và một map()trong một dòng, tôi đã chạy nó với một for...ofvòng lặp cho một triệu lần lặp (sử dụng chuỗi của bạn). Một lớp lót trung bình 1420ms trong khi for...oftrung bình 1150ms trên máy của tôi. Đó không phải là một sự khác biệt không đáng kể, nhưng một lớp lót sẽ hoạt động tốt nếu bạn chỉ thực hiện một số trận đấu.

Xem matchAlltrên caniuse


3

Nếu bạn chỉ muốn tìm vị trí của tất cả các trận đấu, tôi muốn chỉ cho bạn một mẹo nhỏ:

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

Nó có thể không gắn được nếu bạn có RegExp với độ dài thay đổi nhưng đối với một số người, nó có thể hữu ích.

Đây là trường hợp nhạy cảm. Đối với trường hợp phân biệt String.toLowerCasechức năng sử dụng trước đây.


Tôi nghĩ câu trả lời của bạn là câu trả lời hay nhất, bởi vì việc sử dụng RegExp rất nguy hiểm.
Bharata

1

Đây là một mã đơn giản

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

Làm theo câu trả lời của @jcubic, giải pháp của anh ấy đã gây ra một sự nhầm lẫn nhỏ cho trường hợp của tôi.
Ví dụ: var result = indexes('aaaa', 'aa')nó sẽ trả về [0, 1, 2]thay vì [0, 2]
Vì vậy, tôi đã cập nhật một chút giải pháp của anh ấy như bên dưới để phù hợp với trường hợp của tôi

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

Cảm ơn vì tất cả những hồi đáp. Tôi đã xem qua tất cả chúng và tìm ra một hàm cung cấp chỉ số cuối cùng đầu tiên của mỗi lần xuất hiện của chuỗi con 'kim'. Tôi đăng nó ở đây trong trường hợp nó sẽ giúp ai đó.

Xin lưu ý, nó không giống với yêu cầu ban đầu chỉ khi bắt đầu mỗi lần xuất hiện. Nó phù hợp với usecase của tôi hơn vì bạn không cần phải giữ chiều dài kim.

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

Kiểm tra giải pháp này sẽ có thể tìm thấy cùng một chuỗi ký tự, hãy cho tôi biết nếu thiếu hoặc không đúng điều gì đó.

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


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

}

Điều này tìm kiếm các lần xuất hiện của một chuỗi bên trong một chuỗi khác chứ không phải là các biểu thức chính quy.

-1

mã dưới đây sẽ thực hiện công việc cho bạn:

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

Sử dụng String.prototype.match .

Đây là một ví dụ từ chính tài liệu 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']

Việc này thật thẳng thắn.
igaurav

11
Câu hỏi đặt ra là làm thế nào để tìm các chỉ số của các lần xuất hiện, chứ không phải các lần xuất hiện là bản thân của chúng!
Luckylooke

1
dispite câu trả lời này doesnt phù hợp với câu hỏi, nhưng đó là những gì tôi đang tìm kiếm :)
AlexNikonov
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.