【问题标题】:PHP - the fastest way to find multiple keywords in text?PHP - 在文本中查找多个关键字的最快方法?
【发布时间】:2019-06-26 08:35:48
【问题描述】:

我有大量关键字(超过一千个),我需要搜索一个大的 HTML 文件以查找文本中存在哪些关键字。然后我需要返回找到的这些关键字的索引。

例如,如果我的数组是:

$keywords = array("love", "money", "minute", "loop"); // etc.

如果有单词“money”和“loop”的任何实例,我想得到一个数组:

$results = array("1", "3"); // first $keyword element is 0

我尝试使用 preg_match_all,但我不确定如何让 $matches 返回我的关键字的索引。

这是我到目前为止的代码:

$keywords = array("love", "money", "minute", "loop");

$html = file_get_contents($url);

preg_match_all("#(love|money|minute|loop)#i", $html, $matches);

var_dump($matches);

结果如下所示:

array(2) {
  [0]=>
  array(4) {
    [0]=>
    string(6) "minute"
    [1]=>
    string(6) "minute"
    [2]=>
    string(5) "money"
    [3]=>
    string(5) "Money"
  }
  [1]=>
  array(4) {
    [0]=>
    string(6) "minute"
    [1]=>
    string(6) "minute"
    [2]=>
    string(5) "money"
    [3]=>
    string(5) "Money"
  }
}
  1. 在 PHP 中最快/最优化的方法是什么? preg_match_all 好吗?我想避免使用 foreach,这会导致我的函数抓取整个 HTML 超过一千次(不是很有效)。

  2. 如何获取关键字的索引?例如。找到的关键字是 0 号和 3 号,无论其数量多少。

【问题讨论】:

  • 索引?就像字数一样? love peace and coding 如果关键字是 peace 你想要 1 作为回报?
  • 也是code上面字符串的一部分吗?
  • 不,如果您只关心子字符串是否存在,而不关心它存在的位置或存在的次数,那么匹配所有绝对不是最优的。正则表达式比较也比字符串比较慢。
  • preg_match_all 并非旨在告诉您单独数组上匹配项的索引。编写一些代码来获取匹配项并在您的数组中找到它以获取您的索引。它的一个循环......
  • @Andreas 通过索引,我指的是文本中出现了哪些关键字,那么它是关键字 1 还是关键字 2 或关键字 75。

标签: php html arrays regex parsing


【解决方案1】:

您可以使用PREG_OFFSET_CAPTURE 标志来获取偏移量:

$matches=[];
$html = "love and money make the world loop around in a loop three times per minute";
preg_match_all("#love|money|minute|loop#i", $html, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $m) echo $m[0]." found at index ".$m[1]."\n";

// output:
love found at index 0
money found at index 9
loop found at index 30
loop found at index 47
minute found at index 68

现在,它的执行速度足够快 供您评估。如果是这样,那么寻找更复杂的替代方案就没有意义了。

【讨论】:

    【解决方案2】:

    如果您只需要查看文本中出现了哪些关键字,您可以将stripos 映射到关键字数组。

    $result = array_map(function ($keyword) use (&$html) {
        return stripos($html, $keyword) !== false;
    }, $keywords);
    

    现在,stripos 将在另一个字符串中查找一个字符串。它没有单词的概念,如果您不想匹配作为较长单词的一部分存在的关键字,则需要使用带有单词边界的正则表达式。但是您当前使用的表达式并没有这样做,所以这可能不是问题。

    【讨论】:

    • 这不是一次搜索每个关键字的整个内容吗?因此,如果您有 1000 个关键字,它可能会扫描整个字符串 1000 次。虽然它会在找到每个单词后停止扫描 - 如果关键字不存在,它将搜索到字符串的末尾。
    • 虽然简单,但我希望此解决方案的性能比其他答案中的解决方案更差,因为它会扫描超过一千次的大型文档
    【解决方案3】:
    $keywords = array("love", "money", "minute", "loop");
    
    // The function "GetHtmlWords" gets the html content and clean it from spacial 
    // characters
    $htmlWordsArray = explode(' ', GetHtmlWords($url));
    
    // Calculate the intersection - intersect return values while preserving keys
    // use array_keys to get just the keys. double check if first index is 0 or 1
    $result = array_keys(array_intersect($keywords, $htmlWordsArray));
    
    var_dump($result);
    
    // Get the content of the html, cleaned from spacial characters, with space 
    // between words
    function GetHtmlWords($url) {
      $htmlContent = file_get_contents($url);
    
      // Handle , and . that may split between words, without space.
      // for example hi.there first,second
      $html = $str_replace([".",","], " ", $htmlContent);
    
      // Clean the text from spacial characters (including , and .)
      $cleanHtml = preg_replace('/[^A-Za-z0-9\- ]/', '', $html)
    
      // Remove duplicate spaces
      $htmlWordsOnly = $str_replace("  ", " ", $html);
    
      return($htmlWordsOnly);
    }
    

    【讨论】:

      【解决方案4】:

      只是使用str_word_count() 的替代方法,您看不到太多,使用 2 作为第二个参数将字符串拆分为以起始位置为键的数组中的单词。然后使用array_intersect() 将其与关键字匹配...

      $keywords = array("love", "money", "minute", "loop");
      // string courtesy of Joni's answer
      $html = "love and money make the world loop around in a loop three times per minute";
      $words = str_word_count($html, 2);
      $match = array_intersect($words, $keywords);
      print_r($match);
      

      给...

      Array
      (
          [0] => love
          [9] => money
          [30] => loop
          [47] => loop
          [68] => minute
      )
      

      不确定这对任何正则表达式的执行情况如何,只需尝试一下即可。

      或者屏幕空间不足...

      print_r(array_intersect(str_word_count($html, 2), $keywords));
      

      如果您只想要关键字是否存在,只需反转 array_intersect() 中数组的顺序(并且不区分大小写 - 首先使用 strtolower() 转换为小写)...

      $match = array_intersect($keywords, str_word_count(strtolower($html), 1));
      

      这给了...

      Array
      (
          [0] => love
          [1] => money
          [2] => minute
          [3] => loop
      )
      

      最后更新:

      在性能方面,我的解决方案可以通过翻转数组来优化,这样就可以更快地检查键是否存在,而不是扫描每个数组以查找字符串值...

      $match = array_flip(array_intersect_key(array_flip($keywords), array_flip(str_word_count(strtolower($html), 1))));
      

      【讨论】:

        【解决方案5】:
        function textHasKeywords($parr_listOfKeywords = null, $pstr_text = '') {
            $matches=[];
            return preg_match_all("#".implode("|", $parr_listOfKeywords)."#i", $pstr_text, $matches, PREG_OFFSET_CAPTURE);
        }
        

        使用方法:

        $larr_listOfKeywords = array("keyword1", "keyword4");
        $lstr_text = 'keyword1 keyword2 keyword3, keyword5';
        
        if ( textHasKeywords($larr_listOfKeywords, $lstr_text) != false ) {
            echo textHaveKeywords($larr_listOfKeywords, $lstr_text);
        }
        

        如果文本中没有使用关键字,此函数将返回创建的关键字的数量或 false。在此示例中将打印 1,因为仅创建了关键字 1。

        【讨论】:

          猜你喜欢
          • 2016-02-11
          • 1970-01-01
          • 1970-01-01
          • 2010-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-08-18
          相关资源
          最近更新 更多