【问题标题】:Find once and return different strings replaced with different words查找一次并返回用不同单词替换的不同字符串
【发布时间】:2022-10-13 19:16:18
【问题描述】:
const str = 'i love code. i write code everyday.'
const regex = /code/g

const outputJs   = str.replace(regex, 'js'  )
const outputCss  = str.replace(regex, 'css' )
const outputHtml = str.replace(regex, 'html')

在这里,我搜索/code/g 三次并替换为不同的字符串。

有没有办法优化这段代码?

因为如果正则表达式变得复杂并且 str 很长,那么 javascript 引擎将需要一些时间来一次又一次地搜索相同的内容,以替换为不同的单词。

我希望你明白我想要做什么。

编辑:一种解决方法是:

  1. 使用 regex.exec(str) 和一些字符串操作生成一组模板字符串。
  2. 使用这些模板字符串通过在它们之间连接替换字符串来生成输出。

【问题讨论】:

  • 您需要 3 个单独的替换件。没有太多的办法。
  • @TimBiegeleisen 这不是真的,理论上你可以使用 regex.exec() 然后用它返回的索引替换,但不确定它是否值得。
  • @PoulBak 我知道,但是如果您正确计算长度,您仍然可以使用 regex.exec()
  • @AvisekDas 是的,你是对的。您需要跟踪偏移量,但它可能比多次运行正则表达式更快。只要确保它实际上是值得的。
  • @AvisekDas 在这种情况下,您需要创建一个数组['i love ', null, '. i write ', null, ' everyday.'],然后在每一帧中将占位符元素和.join('') 分配给一个字符串。您也可以尝试编译为函数 (new Function('t0', 't1', 'return `'i love ${t0}. i write ${t1} everyday.`;')),但我怀疑这会发生很大变化。

标签: javascript regex performance optimization replace


【解决方案1】:

如果性能至关重要,则可以使用字符串模板执行优化,前提是您可以将输入限制为一组有限的可预测模式。例如,在您的示例代码中,您要替换在文本中出现两次但在其他文本中出现次数未知的字符串文字。如果您可以确定文字出现 0 到 10 次,那么您可以编写 11 个模板并选择合适的模板。

    function interleave(list,word) {
      switch (list.length) {
       case 1:
        return `${list[0]}`; 
       case 2:
        return `${list[0]}${word}${list[1]}`; 
       case 3:
        return `${list[0]}${word}${list[1]}${word}${list[2]}`; 
        // add as many cases as you need
       default:
        return list.join(word);
      }
    }

    let outputJs, outputCss, outputHtml;
    let orig='i love code. i write code everyday.';

    const startTime = Date.now();

    let fragments=orig.split(/code/);
    for (let i=0; i<1000000; i++) {
        outputJs = interleave(fragments,'js');
        outputCss = interleave(fragments,'css');
        outputHtml = interleave(fragments,'html');
    }

    const endTime = Date.now();
    console.log('outputJs:   ' + outputJs);
    console.log('outputCss:  ' + outputCss);
    console.log('outputHtml: ' + outputHtml);
    console.log(`time: ${endTime - startTime} ms`);

添加下面的 cmets 中提到的改进AvisekDas(以允许人们运行并验证性能增益)。

function interleave(list,word) {
    let v=list[0];
    for (let i=1; i<list.length; i++) {
        v+=word+list[i];
    }
    return v;
}

let outputJs, outputCss, outputHtml;
let orig='i love code. i write code everyday.';

const startTime = Date.now();

let fragments=orig.split(/code/);
for (let i=0; i<1000000; i++) {
    outputJs = interleave(fragments,'js');
    outputCss = interleave(fragments,'css');
    outputHtml = interleave(fragments,'html');
}

const endTime = Date.now();
console.log('outputJs:   ' + outputJs);
console.log('outputCss:  ' + outputCss);
console.log('outputHtml: ' + outputHtml);
console.log(`time: ${endTime - startTime} ms`);

【讨论】:

  • 这是一个严肃的优化,它产生了巨大的差异。
  • 没想到arr.join() 没有那么优化。
  • 而不是使用模板文字,我更喜欢使用常规字符串连接(+ 运算符),因为在这个用例中这会稍微快一些并且更具可读性。
【解决方案2】:

您可以使用拆分,并在阵列上进行替换:

const str = 'i love code. i write code everyday.';
let outputJs;
let outputCss;
let outputHtml;
let startTime = new Date();
for(let i = 0; i < 1000000; i++) {
  outputJs = [];
  outputCss = [];
  outputHtml = [];
  str.split(/(code)/).forEach(item => {
    if(item === 'code') {
      outputJs.push('js');
      outputCss.push('css');
      outputHtml.push('html');
    } else {
      outputJs.push(item);
      outputCss.push(item);
      outputHtml.push(item);
    }
  });
  outputJs = outputJs.join('');
  outputCss = outputCss.join('');
  outputHtml = outputHtml.join('');
}
console.log('outputJs:   ' + outputJs);
console.log('outputCss:  ' + outputCss);
console.log('outputHtml: ' + outputHtml);
console.log('time: ' + (new Date() - startTime) + ' ms');
输出:
outputJs:   i love js. i write js everyday.
outputCss:  i love css. i write css everyday.
outputHtml: i love html. i write html everyday.
time: 1652 ms

在我的测试中,1,000,000 次运行耗时 1652 毫秒。

现在让我们将其与不同的替换进行比较:

const str = 'i love code. i write code everyday.';
let outputJs;
let outputCss;
let outputHtml;
let startTime = new Date();
for(let i = 0; i < 1000000; i++) {
  outputJs = str.replace(/(code)/g, 'js');
  outputCss = str.replace(/(code)/g, 'css');
  outputHtml = str.replace(/(code)/g, 'html');
}
console.log('outputJs:   ' + outputJs);
console.log('outputCss:  ' + outputCss);
console.log('outputHtml: ' + outputHtml);
console.log('time: ' + (new Date() - startTime) + ' ms');
输出:
outputJs:   i love js. i write js everyday.
outputCss:  i love css. i write css everyday.
outputHtml: i love html. i write html everyday.
time: 494 ms

在我的测试中,1,000,000 次运行耗时 494 毫秒。如您所见,这快了 5 倍,例如split 方法确实使它变慢了。正则表达式经过高度优化,只有在极端情况下才值得寻找替代方案。

请注意,我在正则表达式中添加了单词边界,目的是避免输入字符串中出现误报,例如codexisocodeinepostcodesarcoderm

【讨论】:

  • 您没有考虑 .join('') 操作所花费的时间
  • @peter 在这里可以大大降低复杂性。您可以使用原始正则表达式const parts = str.split(/code/g) 直接拆分str。然后,您可以使用parts.join('replacement_str') 进行替换。
  • @Bergi:好点,已修复
  • @AvisekDas:我故意添加了单词边界以避免误报,例如codexisocodeinepostcodesarcoderm
  • @AvisekDas:还有其他问题吗?愿意接受答案吗?
【解决方案3】:

这是一个更公平的基准:

  • 基线

    const str = 'i love code. i write code everyday.';
    let outputJs;
    let outputCss;
    let outputHtml;
    const startTime = Date.now();
    for (let i = 0; i < 1000000; i++) {
      outputJs = str.replace(/code/g, 'js');
      outputCss = str.replace(/code/g, 'css');
      outputHtml = str.replace(/code/g, 'html');
    }
    const endTime = Date.now();
    console.log('outputJs:   ' + outputJs);
    console.log('outputCss:  ' + outputCss);
    console.log('outputHtml: ' + outputHtml);
    console.log(`time: ${endTime - startTime} ms`);
  • 分裂一次,加入三次:

    const str = 'i love code. i write code everyday.';
    let outputJs;
    let outputCss;
    let outputHtml;
    const startTime = Date.now();
    for (let i = 0; i < 1000000; i++) {
      const parts = str.split(/code/g);
      outputJs = parts.join('js');
      outputCss = parts.join('css');
      outputHtml = parts.join('html');
    }
    const endTime = Date.now();
    console.log('outputJs:   ' + outputJs);
    console.log('outputCss:  ' + outputCss);
    console.log('outputHtml: ' + outputHtml);
    console.log(`time: ${endTime - startTime} ms`);
  • 拆分一次,使用 forEach 替换,通过空字符串加入(来自@PeterThoeny 的回答)

    const str = 'i love code. i write code everyday.';
    let outputJs;
    let outputCss;
    let outputHtml;
    const startTime = Date.now();
    for (let i = 0; i < 1000000; i++) {
      const jsParts = [];
      const cssParts = [];
      const htmlParts = [];
      str.split(/(code)/).forEach(item => {
        if(item === 'code') {
          jsParts.push('js');
          cssParts.push('css');
          htmlParts.push('html');
        } else {
          jsParts.push(item);
          cssParts.push(item);
          htmlParts.push(item);
        }
      });
      outputJs = jsParts.join('');
      outputCss = cssParts.join('');
      outputHtml = htmlParts.join('');
    }
    const endTime = Date.now();
    console.log('outputJs:   ' + outputJs);
    console.log('outputCss:  ' + outputCss);
    console.log('outputHtml: ' + outputHtml);
    console.log(`time: ${endTime - startTime} ms`);

实际上,原始的简单代码是最快的。正则表达式引擎经过了很好的优化。

【讨论】:

  • 你的答案是正确的,但你的基准是错误的。无需每次都使用正则表达式进行拆分,因为正则表达式不会改变。只有重置价值会发生变化。因此,将const parts = str.split(/code/g); 这一行移出for 循环并查看结果。
  • 顺便说一句,你们为什么要将那些s 添加到正则表达式中?
  • @AvisekDas 哎呀,我从彼得的回答中用s 复制了正则表达式。这可能没有必要——但另一方面,如果你说你有一个非常复杂的正则表达式,你应该用它来测试,而不是用这个玩具例子。
  • @AvisekDas您的问题要求拆分一次然后使用3个不同替换值的代码。基准测试正是这样做的——在一个有很多重复的循环中,所以时间实际上是可测量的(并且随机抢占被平均了)。您显然可以将整个事情移出循环,因为每次迭代都在计算相同的结果……但这不是重点。
  • @AvisekDas如果您想将代码拆分为“准备”和“替换”部分,并且只想测量后者,预先花费任何时间,您当然可以这样做,但问题没有t 表明这一点
【解决方案4】:

如果性能至关重要,则可以使用字符串模板执行优化,前提是您可以将输入限制为一组有限的可预测模式。例如,在您的示例代码中,您要替换在文本中出现两次但在其他文本中出现次数未知的字符串文字。如果您可以确定文字出现 0 到 10 次,那么您可以编写 11 个模板并选择合适的模板。

    function interleave(list,word) {
      switch (list.length) {
       case 1:
        return `${list[0]}`; 
       case 2:
        return `${list[0]}${word}${list[1]}`; 
       case 3:
        return `${list[0]}${word}${list[1]}${word}${list[2]}`; 
        // add as many cases as you need
       default:
        return 'ERROR : No qualifying case found.';
      }
    }

    let outputJs, outputCss, outputHtml;
    let orig='i love code. i write code everyday.';

    const startTime = Date.now();

    let fragments=orig.split(/code/);
    for (let i=0; i<1000000; i++) {
        outputJs = interleave(fragments,'js');
        outputCss = interleave(fragments,'css');
        outputHtml = interleave(fragments,'html');
    }

    const endTime = Date.now();
    console.log('outputJs:   ' + outputJs);
    console.log('outputCss:  ' + outputCss);
    console.log('outputHtml: ' + outputHtml);
    console.log(`time: ${endTime - startTime} ms`);

【讨论】:

    猜你喜欢
    • 2011-07-08
    • 2022-07-05
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 2014-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多