【问题标题】:php match string to multiple array of keywordsphp将字符串匹配到多个关键字数组
【发布时间】:2011-02-05 00:57:18
【问题描述】:

我正在编写一个基本的分类工具,它将获取一个标题,然后将其与一组关键字进行比较。示例:

$cat['dining'] = array('food','restaurant','brunch','meal','cand(y|ies)');
$cat['services'] = array('service','cleaners','framing','printing');
$string = 'Dinner at seafood restaurant';

是否有创造性的方法来循环浏览这些类别或查看哪个类别的匹配度最高?请注意,在 'dining' 数组中,我有正则表达式来匹配单词 candy 的变体。我尝试了以下方法,但是这些类别列表变得很长,我想知道这是否是最好的方法:

$keywordRegex = implode("|",$cat['dining']); 
preg_match_all("/(\b{$keywordRegex}\b)/i",$string,$matches]);

谢谢, 史蒂夫

编辑: 感谢@jmathai,我能够添加排名:

    $matches = array(); 
    foreach($keywords as $k => $v) {
        str_replace($v, '#####', $masterString,$count);
        if($count > 0){
            $matches[$k] = $count;
        }
    }
    arsort($matches);

【问题讨论】:

  • 不太了解 php,但我怀疑散列会比正则表达式更快。如果您将其中一个值作为实际的正则表达式(如 cand(y|ies) 将其作为主题的正则表达式运行,例如将正则表达式值放入单独的哈希中。

标签: php regex arrays


【解决方案1】:

这可以通过一个循环来完成。

为了提高效率,我会将糖果和糖果分成单独的条目。一个聪明的技巧是用一些标记替换匹配项。让我们使用 10 个#。

$cat['dining'] = array('food','restaurant','brunch','meal','candy','candies');
$cat['services'] = array('service','cleaners','framing','printing');
$string = 'Dinner at seafood restaurant';

$max = array(null, 0); // category, occurences
foreach($cat as $k => $v) {
  $replaced = str_replace($v, '##########', $string);
  preg_match_all('/##########/i', $replaced, $matches);
  if(count($matches[0]) > $max[1]) {
    $max[0] = $k;
    $max[1] = count($matches[0]);
  }
}

echo "Category {$max[0]} has the most ({$max[1]}) matches.\n";

【讨论】:

  • 纯天才。我对其进行了一些简化,并在我编辑的帖子中添加了匹配计数。
  • @daxiang28:不过,这样你必须将字符串与$cat 中的每个单词单独匹配,而不是使用字符串中的每个单词快速查找,并且在上面的变体中你只能得到匹配词数最多的第一个类别名称。 (你的改动在这方面更好)。此外,仍然是必须输入所有变体的问题。不过很聪明。
  • 我可能会尝试通过在 preg_match_all 调用中使用 \b 单词边界标记来消除 str_replace 步骤。但是这个问题的一个更令人不安的方面是解决方案的运行时间。随着每个类别数组中元素数量的增加,循环会随着搜索词中的单词数量而增加。转动关联列表中的数组(想想在此处设置)将用内存换取运行时。即便如此,我认为 is 方法存在扩展问题。不过对于小型项目来说可能已经足够了。
【解决方案2】:
$cat['dining'] = array('food','restaurant','brunch','meal');
$cat['services'] = array('service','cleaners','framing','printing');
$string = 'Dinner at seafood restaurant';

$string = explode(' ',$string);
foreach ($cat as $key => $val) {
  $kwdMatches[$key] = count(array_intersect($string,$val));
}
arsort($kwdMatches);

echo "<pre>";
print_r($kwdMatches);

【讨论】:

  • 这不考虑正则表达式?
【解决方案3】:

如果单词的数量不是太多,那么创建一个反向查找表可能是一个想法,然后针对它运行标题。

// One-time reverse category creation
$reverseCat = array();    
foreach ($cat as $cCategory => $cWordList) {
   foreach ($cWordList as $cWord) {
       if (!array_key_exists($cWord, $reverseCat)) {
           $reverseCat[$cWord] = array($cCategory);
       } else if (!in_array($cCategory, $reverseCat[$cWord])) {
           $reverseCat[$cWord][] = $cCategory;
       }
   }
}

// Processing a title
$stringWords = preg_split("/\b/", $string);

$matchingCategories = array();
foreach ($stringWords as $cWord) {
   if (array_key_exists($cWord, $reverseCat)) {
       $matchingCategories = array_merge($matchingCategories, $reverseCat[$cWord]);
   }
}

$matchingCategories = array_unique($matchingCategories);

【讨论】:

  • 请注意,如果需要排名,则不要在最后调用 array_unique(),而是在 $matchingCategories 上快速 O(n) 循环来构建计数表,然后是 arsort()将给出降序排名。
  • 这是否处理“cand(y|ies)”条件?
  • @daxiang28:对不起,我没有注意到,如果你只是写成'candy','candies',那么它会的。你真的需要那里的正则表达式匹配吗?如果您可以在所有目标单词中使用任意正则表达式,那么我认为您必须将$string 中的每个单词与$cat 子数组中的每个单词进行匹配,这非常慢。
  • 反向查找很有趣。但在这种情况下,我是否必须处理这个词的每一个变体?如果我有一个关键字“piece”,但字符串是“4 pieces of candy”,它不会匹配对吗?
  • @daxiang28:正确,您必须输入所有变体。不过,您可以绕过输入常规复数的需要。说所有常规复数形式,您只需在单词末尾加上+,而不是再次写单词。然后在foreach ($cWordList as $cWord) 中,剥离并检测最后的任何+,在检测到的情况下,同时将.'s' 变体添加到$reverseCat。然后你只需要输入不规则的复数形式和替代形式。
【解决方案4】:

您正在执行 O(n*m) 查找,其中 n 是类别的大小,m 是标题的大小。您可以尝试像这样组织它们:

const $DINING = 0;
const $SERVICES = 1;

$categories = array(
    "food" => $DINING,
    "restaurant" => $DINING,
    "service" => $SERVICES,
);

然后对于标题中的每个单词,检查 $categories[$word] 以找到类别 - 这会得到 O(m)。

【讨论】:

    【解决方案5】:

    好的,这是我的新答案,可让您在 $cat[n] 值中使用正则表达式...关于此代码只有一个警告我无法弄清楚...由于某种原因,如果您有任何原因,它会失败$cat[n] 值开头的元字符或字符类。

    例如:.*food 将不起作用。但是s.afoodsea.* 等...或者您的cand(y|ies) 示例将起作用。我有点认为这对你来说已经足够了,因为我认为正则表达式的重点是处理不同时态的单词,并且在这种情况下单词的开头很少改变。

    function rMatch ($a,$b) {
      if (preg_match('~^'.$b.'$~i',$a)) return 0;
      if ($a>$b) return 1;
      return -1;
    }
    
    $string = explode(' ',$string);
    foreach ($cat as $key => $val) {
      $kwdMatches[$key] = count(array_uintersect($string,$val,'rMatch'));
    }
    arsort($kwdMatches);
    
    echo "<pre>";
    print_r($kwdMatches);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-20
      • 1970-01-01
      • 2012-06-10
      • 2019-06-21
      相关资源
      最近更新 更多