在JavaScript中,如何使用regex进行匹配,除非单词在排除的单词列表中

In JavaScript, how can I use regex to match unless words are in a list of excluded words?

本文关键字:单词 列表 排除 JavaScript 何使用 regex      更新时间:2023-09-26

如何使用正则表达式匹配除特定单词列表之外的任何单词(''w)?例如:

我想匹配单词useutilize以及它之后的任何单词,除非这些单词是somethingfish

use this  <-- match
utilize that  <-- match
use something   <-- don't want to match this
utilize fish <-- don't want to match this

如何指定不想匹配的单词列表?

您可以使用负前瞻来确定要匹配的单词不是特定的单词。您可以使用以下正则表达式来执行此操作:

(use|utilize)'s(?!fish|something)('w+)

这将匹配后面跟着空格的"use"或"use",然后如果下面的单词不是"fish"或"something",它将匹配下一个单词。

这应该做到:

/(?:use|utilize)'s+(?!something|fish)'w+/

不要对正则表达式进行硬编码

与其尝试将所有搜索和排除项放在一个硬编码的正则表达式中,不如使用短路求值来选择匹配所需项的字符串,然后拒绝包含不需要项的字符串,这样通常更具可维护性(当然也更具可读性)。

然后,您可以将此测试封装到一个函数中,该函数根据数组的运行时值返回布尔值。例如:

'use strict';
// Join arrays of terms with the alternation operator.
var searchTerms   = new RegExp(['use', 'utilize'].join('|'));
var excludedTerms = new RegExp(['fish', 'something'].join('|'));
// Return true if a string contains only valid search terms without any
// excluded terms.
var isValidStr = function (str) {
    return (searchTerms.test(str) && !excludedTerms.test(str));
};
isValidStr('use fish');       // false
isValidStr('utilize hammer'); // true

有些人在遇到问题时会想"我知道,我会用正则表达式。"

现在他们有两个问题。

正则表达式适用于匹配规则的符号序列,而不是单词。任何lexer+解析器都会更合适。例如,这个任务的语法在Antlr中看起来非常简单。如果你负担不起lexer/parsers后面的学习曲线(对于你的给定任务来说,它们非常容易),那么用正则表达式将文本分割成单词,然后用look先行1进行简单搜索就足够了。

带单词的正则表达式很快就会变得非常复杂。它们很难阅读,也很难理解。

更新:感谢所有的反对票。这是我的意思的一个例子。

import re
def Tokenize( text ):
    return re.findall( "'w+", text )
def ParseWhiteListedWordThenBlackListedWord( tokens, whiteList, blackList ):
    for i in range( 0, len( tokens ) - 1 ):
        if tokens[i] in whiteList and tokens[i + 1] not in blackList:
            yield ( tokens[i], tokens[i + 1] )

以下是一些性能测试:

>>> timeit.timeit( 'oldtime()', 'from __main__ import oldtime', number=1 )
0.02636446265387349
>>> timeit.timeit( 'oldtime()', 'from __main__ import oldtime', number=1000 )
28.80968123656703
>>> timeit.timeit( 'newtime()', 'from __main__ import newtime', number=100 )
44.24506212427741
>>> timeit.timeit( 'newtime11()', 'from __main__ import newtime11', number=1 ) +
timeit.timeit( 'newtime13()', 'from __main__ import newtime13', number=1000 )
103.07938725936083
>>> timeit.timeit( 'newtime11()', 'from __main__ import newtime11', number=1 ) + 
timeit.timeit( 'newtime12()', 'from __main__ import newtime12', number=1000 )
0.3191265909927097

一些注意事项:测试是在简·奥斯汀的《傲慢与偏见》的英文文本上进行的,第一个单词是"先生"answers"我的",第二个词是"贝内特"answers"亲爱的"。

oldtime()是正则表达式。newtime()是Tokenizer+Parser,请注意,它运行了100次,而不是1000次,因此,它的可比时间为~442。

接下来的两个测试是在重用Tokenizer结果时,模拟Parser在同一文本上的重复运行。

newtime11()仅为Tokenizer。newtime13()是将结果转换为列表的Parser(用于模拟结果的遍历)。newtime12()只是Parser。

好吧,正则表达式的速度更快,在单次传递的情况下,甚至在生成器的情况下(在Tokenizer+Parser的情况下大部分时间都花在标记文本上),也快了很多。但是,当您可以重用标记化的文本并懒洋洋地评估解析器结果时,生成器表达式的速度非常快。

有相当多的性能优化是可能的,但这会使解决方案复杂化,可能会使正则表达式成为最佳实现。

令牌化器+解析器方法既有优点也有缺点:-解决方案的结构更复杂(元素更多),但每个元素都更简单-元件易于测试,包括自动测试-它很慢,但通过重复使用相同的文本和懒散地评估结果会变得更好-由于生成器和延迟评估,一些工作可能会被避免-更改白名单和/或黑名单是微不足道的-有几个白名单、几个黑名单和/或它们的组合是微不足道的-添加新的解析器重用标记化器结果是很琐碎的

现在来谈谈棘手的"你不需要它"问题。除非这是一项更大任务的一部分,否则你都不需要原始问题的解决方案。更大的任务应该决定最佳的方法。

更新:在上对词法分析和语法分析中的正则表达式进行了很好的讨论http://commandcenter.blogspot.ru/2011/08/regular-expressions-in-lexing-and.html.我会用报价来总结

鼓励将正则表达式作为所有文本处理的灵丹妙药问题不仅是懒惰和糟糕的工程,它还强化了它们被根本不应该使用它们的人使用。