从字符串内给定索引开始的高效Regexp匹配

zmeyuzjn  于 2023-08-08  发布在  其他
关注(0)|答案(2)|浏览(121)

我已经解析了一个索引为idx的字符串。我的下一个解析步骤使用Regexp。它需要匹配字符串的下一部分,即从位置idx开始。我如何有效地做到这一点?
举例来说:

let myString = "<p>ONE</p><p>TWO</p>"
let idx

// some code not shown here parses the first paragraph
// and updates idx
idx = 10

// next parse step must continue from idx 
let myRegex = /<p>[^<]*<\/p>/
let subbed = myString.substring(idx)
let result = myRegex.exec(subbed)
console.log(result) // "<p>TWO</p>", not "<p>ONE</p>"

字符串
但是myString.substring(idx)似乎是一个相当昂贵的操作。
有没有这样的正则表达式操作:result = myRegex.execFromIndex(idx, myString);
一般来说,我想从不同的索引开始正则表达式匹配,这样我就可以排除字符串的一部分,避免已经解析过的匹配。所以一次可以是myString[0],另一次是myString[51],依此类推。
有没有一种方法可以有效地做到这一点?我正在解析成千上万的行,并希望以尽可能便宜的方式完成这一工作。

wlwcrazw

wlwcrazw1#

JavaScript Regexp有一个lastIndex属性,它在Regexp.exec()中用作占位符,包含最后一个匹配的索引,表明它知道下一步从哪里开始。因此,设置myRegex.lastIndex = 3应该可以解决您的问题。
它比substring方法更有效,因为它不需要创建额外的变量,并且设置lastIndex属性可能比执行子字符串操作更快。其他的一切都和你做的一样。
下面是一个测试,因为它表明设置lastIndex将产生与首先执行substring相同的结果。

var result1Elem = document.getElementById('result1');
var result2Elem = document.getElementById('result2');
var runBtn = document.getElementById('RunBtn');
runBtn.addEventListener("click", runTest);
function runTest() {
  var substrStart = +document.getElementById('substrStartText').value
  var myRegex1 = new RegExp(document.getElementById('regexText').value, 'g');
  myRegex1.lastIndex = substrStart;
  var myRegex2 = new RegExp(document.getElementById('regexText').value, 'g');

  var myString1 = document.getElementById('testText').value;
  var myString2 = myString1.substring(3);
  
  var result;
  
  var safety = 0;
  while ((result = myRegex1.exec(myString1)) !== null) {
    result1Elem.innerHTML += '<li>' + result[0] + ' at ' + result.index + '</li>';
    if (safety++ > 50) break;
  }
  
  safety = 0;
  while ((result = myRegex2.exec(myString2)) !== null) {
    result2Elem.innerHTML += '<li>' + result[0] + ' at ' + (result.index + substrStart)  + '</li>';
    if (safety++ > 50) break;
  }
}

个字符

jutyujz0

jutyujz02#

使用Regexp.execlastIndex

  • 使用y or g flag创建Regexp
  • 如果使用y标志,则匹配必须从指定的开始索引处开始
  • 使用g标志,匹配可以发生在指定索引之后的任何位置
  • 将其lastIndex属性设置为开始索引
  • 调用exec

我已经将上面的步骤应用到了你的示例代码中:

let myString = "<p>ONE</p><p>TWO</p>"
let idx

// some code not shown here parses the first paragraph
// and updates idx
idx = 10

// next parse step must continue from idx 
let myRegex = /<p>[^<]*<\/p>/y  // 🚩note the 'y' flag!🚩
myRegex.lastIndex = idx
let result = myRegex.exec(myString)
console.log(result) // "<p>TWO</p>", not "<p>ONE</p>"

字符串
另一个需要知道的有用信息是,exec将更新lastIndex,使其指向字符串中 * 在 * 返回的匹配之后的位置。这允许您执行许多操作,包括:
1.重新运行相同的Regexp,它将自动查找最后一个匹配之后的下一个匹配。
1.如果下一个要解析的内容具有不同的模式,则将lastIndex值转移到不同的Regexp。
1.将lastIndex值复制到非正则表达式解析所使用的变量中。
1.将lastIndex返回给函数的调用方,这样调用方就可以按照自己的意愿处理字符串的其余部分。

为什么string.slicesubstring也是很好的解决方案

但是myString.substring(idx)似乎是一个相当昂贵的操作。
不一定!尽管它们可能不会像Rust那样快,但所有领先的JavaScript引擎(SpiderMonkey,V8,JavaScriptCore)都完全符合您对Rust的描述。他们在幕后优化string.slicesubstring,使用指向源字符串的指针,而不是复制。
Adventures in the land of substrings and RegExps有很多很棒的细节,图片和分析,但它已经五年了,事情可能会变得更好。下面是这个StackOverflow问题的答案:Is Javascript substring virtual?

相关问题