带有preg_match和foreach的PHP标记系统

时间:2017-05-19 13:25:46

标签: php regex preg-match-all keyword-search word-boundary

我正在尝试为我的网站构建这个标记系统,在那里它检查书面文章(可能是400-1000个单词),对于特定的单词,并从数组中创建一个包含所有关键字的字符串。

我制作的那个工作正常,但我想解决一些问题。

$a = "This is my article and it's about apples and pears. I like strawberries as well though.";

$targets = array('apple', 'apples','pear','pears','strawberry','strawberries','grape','grapes');
foreach($targets as $t)
{
   if (preg_match("/\b" . $t . "\b/i", $a)) {
    $b[] = $t;
   }
}
echo $b[0].",".$b[1].",".$b[2].",".$b[3];
$tags = $b[0].",".$b[1].",".$b[2].",".$b[3];

首先,我想知道,如果有任何办法,我可以使这更有效。我有一个大约有5000个关键字的数据库,并且日复一日地扩展。

你可以看到,我不知道如何获得所有的比赛。我正在写$ b [0],$ b [1]等等。

我希望只使用所有匹配创建一个字符串 - 但每次匹配只有一次。如果提到苹果5次,那么只有1个应该进入字符串。

A说 - 这有效。但我觉得,这不是最佳解决方案。

修改

我现在正在尝试这个,但我根本无法让它工作。

$a = "This is my article and it's about apples and pears. I like strawberries as well though.";

$targets = array('apple', 'apples','pear','pears','strawberry','strawberries','grape','grapes');
$targets = implode('|', $targets);
$b = [];
preg_match("/\b(" . $targets . ")\b/i", $a, $b);

echo $b;

2 个答案:

答案 0 :(得分:1)

首先,我想提供一种非正则表达式方法,然后我会进入一些冗长的正则表达式的考虑。

因为你的搜索"针"完整的话,你可以像str_word_count()一样利用 magic

代码:(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的回答可能会让你 得到正确的结果,但它几乎没有尝试提高效率。

我要说两点:

  1. preg_match()专门用于捕获单个调用中的多个匹配项时,请勿在循环中使用preg_match_all()。 (稍后将在答案中提供的代码)

  2. 尽可能地压缩你的模式逻辑......

  3. 我们假设您有这样的输入:

    $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
    

    此技术仅处理最简单的情况。您可以通过仔细检查标签列表并识别相关标签并将其缩小来提高性能。

    执行我的上述方法在每个页面加载时压缩管道模式将花费用户加载时间。我强烈建议保留一个不断增长的标签的数据库表,这些标签存储为regex-ified标签。遇到/生成新标签时,请自动将它们单独添加到表中。您应该定期查看~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 =>   ' strawberry',3 => ' apples',4 => ' pears',5 => '草莓&#39 ;,   )

    ...如果你已经设法在我的帖子中阅读了这篇文章,那么你可能会遇到类似OP的问题而且你想要在没有遗憾/错误的情况下前进。有关边缘案例注意事项和方法逻辑的更多信息,请转到my related Code Review post

答案 1 :(得分:0)

key已保存匹配项。所以:

*Templates> :t $(curryN 25)
$(curryN 25)
  :: (t25
      -> t24
      -> t23
      -> t22
      -> t21
      -> t20
      -> t19
      -> t18
      -> t17
      -> t16
      -> t15
      -> t14
      -> t13
      -> t12
      -> t11
      -> t10
      -> t9
      -> t8
      -> t7
      -> t6
      -> t5
      -> t4
      -> t3
      -> t2
      -> t1
      -> t)
     -> (t25, t24, t23, t22, t21, t20, t19, t18, t17, t16, t15, t14,
         t13, t12, t11, t10, t9, t8, t7, t6, t5, t4, t3, t2, t1)
     -> t

3参数已经保存了匹配项,请更改:

int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )

对此:

if (preg_match("/\b" . $t . "\b/i", $a)) {
    $b[] = $t;
}

但是,如果您直接比较该单词,则文档会使用preg_match建议。

  

提示
  如果您只想检查另一个字符串中是否包含一个字符串,请不要使用preg_match()。使用strpos()代替,因为它会更快。

修改

如果你仍然希望通过这样做来使用$matches = []; preg_match("/\b" . $t . "\b/i", $a, $matches); $b = array_merge($b, $matches); ,那么你可以改进(性能)你的代码,替换它:

preg_match

有了这个:

$targets = array('apple', 'apples','pear','pears','strawberry','strawberries','grape','grapes');
foreach($targets as $t)
{
   if (preg_match("/\b" . $t . "\b/i", $a)) {
    $b[] = $t;
   }
}

在这里,您将$targets = array('apple', 'apples','pear','pears','strawberry','strawberries','grape','grapes'); $targets = implode('|', $targets); preg_match("/\b(" . $t . ")\b/i", $a, $matches); 加入$targets(管道),因此您的正则表达式是这样的:|所以您只进行一次搜索,而不是那个foreach。