如何使用正则表达式使用Javascript替换字符串中特定单词之外的所有内容

问题描述 投票:4回答:4

想象一下,你有一个像这样的字符串:“这是一个带有单词的句子。”

我有像$wordList = ["sentence", "words"];这样的单词

我想强调一下列表中没有的单词。这意味着我需要找到并替换其他所有内容,我似乎无法用RegEx来解决如何做到这一点(如果可能的话)。

如果我想匹配我可以做的事情:

text = text.replace(/(sentence|words)\b/g, '<mark>$&</mark>');

(它会将匹配的单词包装在“mark”标签中,并假设我有一些css for <mark>,突出显示它们),它们完美无缺。但我需要相反的!我需要它基本上选择整个字符串,然后排除列出的单词。我试过/^((?!sentence|words)*)*$/gm,但这给了我一个奇怪的无限问题,因为我觉得它太开放了。

拿这个原始句子,我希望最终得到的是"<mark> This is a </mark> sentence <mark> with some </mark> words."

基本上包装(通过替换)除列出的单词之外的所有内容。

我可以得到的最接近的是/^(?!sentence|words).*\b/igm,如果一行以其中一个单词开头(忽略整行),它将成功地完成。

总结一下:1)取一个字符串2)取一个单词列表3)替换字符串中的所有内容,除了单词列表。

可能? (jQuery已经加载了其他东西,所以原始JS或jQuery都可以接受)。

javascript jquery regex string
4个回答
5
投票

从单词列表创建正则表达式。 然后用正则表达式替换字符串。 (这是一个棘手的正则表达式)

var wordList = ["sentence", "words"];

// join the array into a string using '|'.  
var str = wordList.join('|');
// finalize the string with a negative assertion
str = '\\W*(?:\\b(?!(?:' + str + ')\\b)\\w+\\W*|\\W+)+';

//create a regex from the string
var Rx = new RegExp( str, 'g' );
console.log( Rx ); 

var text = "%%%555This is a sentence with words, but not sentences ?!??!!...";
text = text.replace( Rx, '<mark>$&</mark>');

console.log( text );

产量

/\W*(?:\b(?!(?:sentence|words)\b)\w+\W*|\W+)+/g
<mark>%%%555This is a </mark>sentence<mark> with </mark>words<mark>, but not sentences ?!??!!...</mark>

附录

上面的正则表达式假定单词列表仅包含单词字符。 如果不是这种情况,您必须匹配单词以提前匹配位置 过去他们。使用简化的正则表达式和回调函数可以轻松完成此操作。

var wordList = ["sentence", "words", "won't"];

// join the array into a string using '|'.  
var str = wordList.join('|');
str = '([\\S\\s]*?)(\\b(?:' + str + ')\\b|$)';

//create a regex from the string
var Rx = new RegExp( str, 'g' );
console.log( Rx ); 

var text = "%%%555This is a sentence with words, but won't be sentences ?!??!!...";

// Use a callback to insert the 'mark'
text = text.replace(
        Rx,
        function(match, p1,p2)
        {
           var retStr = '';
           if ( p1.length > 0 )
              retStr = '<mark>' + p1 + '</mark>';
           return retStr + p2;
        }
      );

console.log( text );

产量

/([\S\s]*?)(\b(?:sentence|words|won't)\b|$)/g
<mark>%%%555This is a </mark>sentence<mark> with </mark>words<mark>, but 
</mark>won't<mark> be sentences ?!??!!...</mark>

3
投票

您仍然可以在肯定匹配上执行替换,但是反转关闭/打开标记,并在开头添加一个开始标记,在字符串末尾添加一个结束标记。我在这里使用你的正则表达式,这可能是你想要的任何东西,所以我认为它正确匹配需要匹配的东西:

var text = "This is a sentence with words.";

text = "<mark>" + text.replace(/\b(sentence|words)\b/g, '</mark>$&<mark>') + "</mark>";

// If empty tags bother you, you can add:
text = text.replace(/<mark><\/mark>/g, "");

console.log(text);

Time Complexity

在下面的评论中,有人指出第二次替换(可选)是浪费时间。但它具有线性时间复杂度,如下面的片段所示,该片段描述了增加字符串大小的持续时间。

X轴表示输入字符串中的字符数,Y轴表示在此类输入字符串上使用/<mark><\/mark>/g执行替换所需的毫秒数:

// Reserve memory for the longest string
const s = '<mark></mark>' + '<mark>x</mark>'.repeat(2000);
    regex = /<mark><\/mark>/g,
    millisecs = {};
// Collect timings for several string sizes:
for (let size = 100; size < 25000; size+=100) {
	millisecs[size] = test(15, 8, _ => s.substr(0, size).replace(regex, ''));
}
// Show results in a chart:
chartFunction(canvas, millisecs, "len", "ms");

// Utilities
function test(countPerRun, runs, f) {
    let fastest = Infinity;
    for (let run = 0; run < runs; run++) {
        const started = performance.now();
        for (let i = 0; i < countPerRun; i++) f();
        // Keep the duration of the fastest run:
        fastest = Math.min(fastest, (performance.now() - started) / countPerRun);
    }
    return fastest;
}

function chartFunction(canvas, y, labelX, labelY) {
    const ctx = canvas.getContext('2d'),
        axisPix = [40, 20],
        largeY = Object.values(y).sort( (a, b) => b - a )[
                    Math.floor(Object.keys(y).length / 10)
                ] * 1.3; // add 30% to value at the 90th percentile 
        max = [+Object.keys(y).pop(), largeY],
        coeff = [(canvas.width-axisPix[0]) / max[0], (canvas.height-axisPix[1]) / max[1]],
        textAlignPix = [-8, -13];
    ctx.translate(axisPix[0], canvas.height-axisPix[1]);
    text(labelY + "/" + labelX, [-5, -13], [1, 1], false, 2);
    // Draw axis lines
    for (let dim = 0; dim < 2; dim++) {
        const c = coeff[dim], world = [c, 1];
        let interval = 10**Math.floor(Math.log10(60 / c));
        while (interval * c < 30) interval *= 2;
        if (interval * c > 60) interval /= 2;
        let decimals = ((interval+'').split('.')[1] || '').length;
        line([[0, 0], [max[dim], 0]], world, dim);
        for (let x = 0; x <= max[dim]; x += interval) {
            line([[x, 0], [x, -5]], world, dim);
            text(x.toFixed(decimals), [x, textAlignPix[1-dim]], world, dim, dim+1);
        }
    }
    // Draw function
    line(Object.entries(y), coeff);

    function translate(coordinates, world, swap) {
        return coordinates.map( p => {
            p = [p[0] * world[0], p[1] * world[1]];
            return swap ? p.reverse() : p;
        });
    }
    
    function line(coordinates, world, swap) {
        coordinates = translate(coordinates, world, swap);
        ctx.beginPath();
        ctx.moveTo(coordinates[0][0], -coordinates[0][1]);
        for (const [x, y] of coordinates.slice(1)) ctx.lineTo(x, -y);
        ctx.stroke();
    }

    function text(s, p, world, swap, align) { // align: 0=left,1=center,2=right
        const [[x, y]] = translate([p], world, swap);
        ctx.font = '9px courier';
        ctx.fillText(s, x - 2.5*align*s.length, 2.5-y);
    }
}
<canvas id="canvas" width="600" height="200"></canvas>

对于每个字符串大小(以100个字符为步长递增),测量运行正则表达式15次的时间。该测量重复8次,并且在图中报告最快运行的持续时间。在我的电脑上,正则表达式在25μs的字符串上运行25000个字符(由<mark>标签组成)。所以不用担心;-)

您可能会在图表中看到一些峰值(由于浏览器和操作系统干扰),但整体趋势是线性的。鉴于主正则表达式具有线性时间复杂度,总体时间复杂度不会受到它的负面影响。

但是,可选的部分可以在没有正则表达式的情况下执

if (text.substr(6, 7) === '</mark>') text = text.substr(13);
if (text.substr(-13, 6) === '<mark>') text = text.substr(0, text.length-13);

由于JavaScript引擎如何处理字符串(不可变),这个较长的代码在恒定时间内运行。

当然,它不会改变整体时间复杂度,而复杂性仍然是线性的。


1
投票

我不确定这是否适用于所有情况,但对于给定的字符串它。

let s1 = "This is a sentence with words.";
let wordList = ["sentence", "words"];

let reg = new RegExp("([\\s\\S]*?)(" + wordList.join("|") + ")", "g");

console.log(s1.replace(reg, "<mark>$1</mark>$2"))

1
投票

以相反的方式做到:标记所有内容并取消标记您拥有的匹配单词。

text = `<mark>${text.replace(/\b(sentence|words)\b/g, '</mark>$&<mark>')}</mark>`;

Negated regex可能但效率低下。事实上,正则表达式不是正确的工具。可行的方法是遍历字符串并手动构造结束字符串:

//var text = "This is a sentence with words.";
//var wordlist = ["sentence", "words"];
var result = "";
var marked = false;
var nextIndex = 0;

while (nextIndex != -1) {
    var endIndex = text.indexOf(" ", nextIndex + 1);
    var substring = text.slice(nextIndex, endIndex == -1 ? text.length : endIndex);
    var contains = wordlist.some(word => substring.includes(word));
    if (!contains && !marked) {
        result += "<mark>";
        marked = true;
    }
    if (contains && marked) {
        result += "</mark>";
        marked = false;
    }
    result += substring;
    nextIndex = endIndex;
}

if (marked) {
    result += "</mark>";
}
text = result;
© www.soinside.com 2019 - 2024. All rights reserved.