基于JavaScript正则实现多字符串逻辑匹配及查询关键词提取咨询
嘿,刚好我之前做过类似的复合逻辑文本匹配需求,用JavaScript正则完全可以实现,我给你拆解下具体的实现思路和代码,保证好用!
实现支持AND/OR/NEAR/n/P/n的复合字符串匹配
首先得先明确每个逻辑的定义,避免后续实现出现歧义:
- AND:所有指定关键词必须出现在目标字符串中(顺序不限)
- OR:任意一个关键词出现在目标字符串中即可
- NEAR/n:两个关键词之间的距离不超过
n个单词(顺序可以互换) - P/n:两个关键词之间的距离不超过
n个单词,且必须保持指定的先后顺序
第一步:解析查询字符串,提取结构化的关键词与逻辑
用户输入的查询字符串是动态的,比如"apple AND banana NEAR/3 grape P/2 cherry",我们需要先把它拆成关键词和对应的逻辑运算符,方便后续转换成正则。
function parseQuery(query) { // 匹配所有逻辑运算符:AND/OR/NEAR/数字/P/数字 const operatorRegex = /\b(AND|OR|NEAR\/\d+|P\/\d+)\b/gi; // 拆分查询为关键词和运算符,过滤空内容 const parts = query.split(operatorRegex).map(part => part.trim()).filter(Boolean); const parsedResult = []; let currentKeyword = null; for (const part of parts) { if (operatorRegex.test(part)) { if (!currentKeyword) throw new Error('查询格式错误:不能以运算符开头'); parsedResult.push({ keyword: currentKeyword, operator: part }); currentKeyword = null; } else { currentKeyword = part; } } // 处理最后一个未配对的关键词 if (currentKeyword) parsedResult.push({ keyword: currentKeyword }); return parsedResult; }
这个函数会返回一个结构化数组,比如输入上述示例查询,会得到类似:
[ { keyword: "apple", operator: "AND" }, { keyword: "banana", operator: "NEAR/3" }, { keyword: "grape", operator: "P/2" }, { keyword: "cherry" } ]
第二步:将结构化逻辑转换为正则表达式
接下来要把每个逻辑单元转换成对应的正则片段,这里需要注意逻辑优先级:NEAR/P > AND > OR,所以要合理分组处理。
先写几个辅助工具函数
- 转义关键词中的正则特殊字符(避免
.、*等符号干扰匹配):
function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
- 生成
NEAR/n对应的正则(支持关键词顺序互换):
function createNearRegex(keywordA, keywordB, maxDistance) { const escapedA = escapeRegex(keywordA); const escapedB = escapeRegex(keywordB); // 这里的单词模式可以根据需求调整,比如支持带标点的单词 const wordPattern = '[\\w\\s\\'-]+?'; // 匹配A在B前或B在A前,中间最多maxDistance个单词 return `((${escapedA})(${wordPattern}){0,${maxDistance}}(${escapedB})|(${escapedB})(${wordPattern}){0,${maxDistance}}(${escapedA}))`; }
- 生成
P/n对应的正则(固定关键词顺序):
function createPhraseRegex(keywordA, keywordB, maxDistance) { const escapedA = escapeRegex(keywordA); const escapedB = escapeRegex(keywordB); const wordPattern = '[\\w\\s\\'-]+?'; // 匹配A在前,B在后,中间最多maxDistance个单词 return `(${escapedA})(${wordPattern}){0,${maxDistance}}(${escapedB})`; }
组合成最终正则的主函数
function queryToRegex(query) { const parsed = parseQuery(query); if (parsed.length === 0) return /^$/; // 空查询匹配空字符串 let regexFragments = []; let currentOrGroup = []; for (let i = 0; i < parsed.length; i++) { const currentItem = parsed[i]; const nextItem = parsed[i + 1]; if (!nextItem) { // 处理最后一个关键词,加入当前OR组 currentOrGroup.push(escapeRegex(currentItem.keyword)); regexFragments.push(`(${currentOrGroup.join('|')})`); break; } const operator = nextItem.operator; if (operator.startsWith('NEAR/')) { // 处理NEAR逻辑,和下一个关键词组合 const maxDistance = parseInt(operator.split('/')[1], 10); const nearFragment = createNearRegex(currentItem.keyword, nextItem.keyword, maxDistance); currentOrGroup.push(nearFragment); i++; // 跳过下一个关键词,因为已经组合完成 } else if (operator.startsWith('P/')) { // 处理P逻辑,和下一个关键词组合 const maxDistance = parseInt(operator.split('/')[1], 10); const phraseFragment = createPhraseRegex(currentItem.keyword, nextItem.keyword, maxDistance); currentOrGroup.push(phraseFragment); i++; // 跳过下一个关键词 } else if (operator === 'AND') { // AND逻辑用正向预查保证所有关键词都存在,加入正则片段 currentOrGroup.push(escapeRegex(currentItem.keyword)); regexFragments.push(`(?=.*${currentOrGroup.join('')})`); currentOrGroup = []; // 重置OR组 } else if (operator === 'OR') { // OR逻辑加入当前OR组,后续统一用|连接 currentOrGroup.push(escapeRegex(currentItem.keyword)); } } // 组合所有片段,生成最终正则(gi模式:全局匹配+忽略大小写,可按需调整) const finalRegexStr = regexFragments.join('') + (currentOrGroup.length ? `(${currentOrGroup.join('|')})` : ''); return new RegExp(finalRegexStr, 'gi'); }
第三步:使用示例与关键词提取
匹配测试
// 示例查询 const userQuery = "apple AND banana NEAR/2 cherry OR orange P/1 juice"; const matchRegex = queryToRegex(userQuery); // 测试字符串 const testStr1 = "I have apple, banana, and cherry in my lunch bag"; // 匹配apple AND banana NEAR/2 cherry const testStr2 = "Fresh orange juice tastes great in summer"; // 匹配orange P/1 juice const testStr3 = "I love apple and orange"; // 匹配OR分支的orange console.log(matchRegex.test(testStr1)); // true console.log(matchRegex.test(testStr2)); // true console.log(matchRegex.test(testStr3)); // true
提取查询中的关键词
如果需要单独提取用户查询里的所有关键词,可以基于parseQuery函数扩展:
function extractQueryKeywords(query) { const parsed = parseQuery(query); // 去重后返回关键词数组 return [...new Set(parsed.map(item => item.keyword))]; } // 示例:提取关键词 console.log(extractQueryKeywords(userQuery)); // ["apple", "banana", "cherry", "orange", "juice"]
注意事项与优化方向
- 单词定义:示例中的
wordPattern是[\w\s\'-]+?,如果需要支持更多特殊字符(比如中文、表情),可以调整为更通用的模式,比如[\S\s]+?(但要注意性能)。 - 括号嵌套支持:当前解析函数不支持带括号的查询(比如
(apple OR banana) AND cherry),如果需要支持,可以扩展解析逻辑,用栈来处理分组。 - 性能优化:如果目标字符串非常长或者查询关键词极多,纯正则可能会有性能瓶颈,可以分阶段匹配:先用AND逻辑过滤掉不满足的字符串,再用NEAR/P和OR逻辑做精细匹配。
- 错误处理:可以在
parseQuery中加入更多格式校验,比如避免连续运算符、无效的NEAR/P参数等。
内容的提问来源于stack exchange,提问作者MokiNex




