首先,我想提供一个非正则表达式的方法,然后我将讨论一些冗长的正则表达式考虑。
因为您的搜索“针”是完整的词,您可以像这样利用str_word_count() 的魔法:
代码:(Demo)
$targets=['apple','apples','pear','pears','strawberry','strawberries','grape','grapes']; // all lowercase
$input="Apples, pears, and strawberries are delicious. I probably favor the flavor of strawberries most. My brother's favorites are crabapples and grapes.";
$lowercase_input=strtolower($input); // eliminate case-sensitive issue
$words=str_word_count($lowercase_input,1); // split into array of words, permitting: ' and -
$unique_words=array_flip(array_flip($words)); // faster than array_unique()
$targeted_words=array_intersect($targets,$unique_words); // retain matches
$tags=implode(',',$targeted_words); // glue together with commas
echo $tags;
echo "\n\n";
// or as a one-liner
echo implode(',',array_intersect($targets,array_flip(array_flip(str_word_count(strtolower($input),1)))));
输出:
apples,pears,strawberries,grapes
apples,pears,strawberries,grapes
现在关于正则表达式...
虽然 matiaslauriti 的回答可能会为您提供正确结果,但它很少尝试提供任何大的效率提升。
我要说明两点:
当 preg_match_all() 专门设计用于在单个调用中捕获多次出现时,请勿在循环中使用 preg_match()。 (代码稍后在回答中提供)
尽可能地压缩你的模式逻辑...
假设您有这样的输入:
$input="Today I ate an apple, then a pear, then a strawberry. This is my article and it's about apples and pears. I like strawberries as well though.";
如果你使用这个标签数组:
$targets=['apple','apples','pear','pears','strawberry','strawberries','grape','grapes'];
生成一个简单的管道正则表达式模式,例如:
/\b(?:apple|apples|pear|pears|strawberry|strawberries|grape|grapes)\b/i
正则表达式引擎需要 677 步 来匹配 $input 中的所有水果。 (Demo)
相比之下,如果你像这样使用? 量词压缩标签元素:
\b(?:apples?|pears?|strawberry|strawberries|grapes?)\b
您的模式变得简洁高效,只需 501 步即可获得相同的预期结果。 (Demo)
可以通过编程方式为简单的关联(包括复数和动词变位)生成这种压缩模式。
这是处理单数/复数关系的方法:
foreach($targets as $v){
if(substr($v,-1)=='s'){ // if tag ends in 's'
if(in_array(substr($v,0,-1),$targets)){ // if same words without trailing 's' exists in tag list
$condensed_targets[]=$v.'?'; // add '?' quantifier to end of tag
}else{
$condensed_targets[]=$v; // add tag that is not plural (e.g. 'dress')
}
}elseif(!in_array($v.'s',$targets)){ // if tag doesn't end in 's' and no regular plural form
$condensed_targets[]=$v; // add tag with irregular pluralization (e.g. 'strawberry')
}
}
echo '/\b(?:',implode('|',$condensed_targets),")\b/i\n";
// /\b(?:apples?|pears?|strawberry|strawberries|grapes?)\b/i
这种技术只会处理最简单的情况。您可以通过检查标签列表并识别相关标签并压缩它们来真正提高性能。
执行我的上述方法以在每次页面加载时压缩管道模式会花费您的用户加载时间。我非常强烈的建议是保留您不断增长的标签的数据库表,这些标签存储为正则表达式标签。当遇到/生成新标签时,自动将它们单独添加到表中。您应该定期查看大约 5000 个关键字,并找出可以合并而不会丢失准确性的标签。
它甚至可以帮助您维护数据库表逻辑,如果您有一列用于正则表达式模式,另一列显示该行的正则表达式模式包含的内容的 csv:
---------------------------------------------------------------
| Pattern | Tags |
---------------------------------------------------------------
| apples? | apple,apples |
---------------------------------------------------------------
| walk(?:s|er|ed|ing)? | walk,walks,walker,walked,walking |
---------------------------------------------------------------
| strawberry | strawberry |
---------------------------------------------------------------
| strawberries | strawberries |
---------------------------------------------------------------
为了提高效率,您可以通过合并草莓和草莓行来更新表数据,如下所示:
---------------------------------------------------------------
| strawberr(?:y|ies) | strawberry,strawberries |
---------------------------------------------------------------
有了这么简单的改进,如果你只检查$input这两个标签,所需的步骤从59下降到40。
由于您要处理 >5000 个标签,因此性能提升将非常显着。这种细化最好在人工层面上处理,但您可以使用一些编程技术来识别共享内部子字符串的标签。
当您想使用您的 Pattern 列值时,只需将它们从您的数据库中拉出,将它们放在一起,然后将它们放在 preg_match_all() 中。
*请记住,在将标签压缩为单个模式时,您应该使用非捕获组,因为我要遵循的代码将通过避免捕获组来减少内存使用。
代码(Demo Link):
$input="Today I ate an apple, then a pear, then a strawberry. This is my article and it's about apples and pears. I like strawberries as well though.";
$targets=['apple','apples','pear','pears','strawberry','strawberries','grape','grapes'];
//echo '/\b(?:',implode('|',$targets),")\b/i\n";
// condense singulars & plurals forms using ? quantifier
foreach($targets as $v){
if(substr($v,-1)=='s'){ // if tag ends in 's'
if(in_array(substr($v,0,-1),$targets)){ // if same words without trailing 's' exists in tag list
$condensed_targets[]=$v.'?'; // add '?' quantifier to end of tag
}else{
$condensed_targets[]=$v; // add tag that is not plural (e.g. 'dress')
}
}elseif(!in_array($v.'s',$targets)){ // if tag doesn't end in 's' and no regular plural form
$condensed_targets[]=$v; // add tag with irregular pluralization (e.g. 'strawberry')
}
}
echo '/\b(?:',implode('|',$condensed_targets),")\b/i\n\n";
// use preg_match_all and call it just once without looping!
$tags=preg_match_all("/\b(?:".implode('|',$condensed_targets).")\b/i",$input,$out)?$out[0]:null;
echo "Found tags: ";
var_export($tags);
输出:
/\b(?:苹果?|梨?|草莓|草莓|葡萄?)\b/i
找到标签:数组(0 => 'apple', 1 => 'pear', 2 =>
'草莓', 3 => '苹果', 4 => '梨', 5 => '草莓',
)
...如果您已经成功阅读了我的帖子,那么您可能遇到了像 OP 这样的问题,并且您希望继续前进而不会后悔/错误。请转至my related Code Review post,了解有关边缘案例注意事项和方法逻辑的更多信息。