2010-04-13 120 views
5

什麼是PHP的最快速的方式採取關鍵字列表並匹配它的搜索結果(如標題數組)所有詞最快的PHP例程匹配詞

舉例來說,如果我的關鍵字詞組是「大皮鞋」,那麼下面的標題比賽...

  • 找一些真正大皮鞋
  • 皮鞋很好
  • 很好天!這些都是一些酷皮鞋
  • ,製造皮革,可

...而這些不會匹配:

  • 皮鞋上出售今天!
  • 你會愛上這些皮鞋大大
  • 大鞋不便宜

我想有一些技巧與陣列功能或正則表達式(正則表達式)來快速實現這一目標。

+1

我會使用爆炸,array_merge/array_unique和計數這一組合,但我不知道它有多快。 – svens 2010-04-13 20:35:41

回答

4

我會用在標題和測試字的索引,如果每個搜索字詞在該指數:

$terms = explode(' ', 'great leather shoes'); 
$titles = array(
    'Get Some Really Great Leather Shoes', 
    'Leather Shoes Are Great', 
    'Great Day! Those Are Some Cool Leather Shoes!', 
    'Shoes, Made of Leather, Can Be Great' 
); 
foreach ($titles as $title) { 
    // extract words in lowercase and use them as key for the word index 
    $wordIndex = array_flip(preg_split('/\P{L}+/u', mb_strtolower($title), -1, PREG_SPLIT_NO_EMPTY)); 
    // look up if every search term is in the index 
    foreach ($terms as $term) { 
     if (!isset($wordIndex[$term])) { 
      // if one is missing, continue with the outer foreach 
      continue 2; 
     } 
    } 
    // echo matched title 
    echo "match: $title"; 
} 
+1

對於unicode支持爲+1。 – 2010-04-13 22:45:24

1

我不能爲您提供一個明確的答案,但我想嘗試該基準所建議,並會與鏈接一些in_array我們一起開始新的解決方案。

if (in_array('great', $list) && in_array('leather', $list) && in_array('shoes', $list)) { 
    // Do something 
} 
3

可以preg_grep()您對某事陣列狀

/^(?=.*?\bgreat)(?=.*?\bleather)(?=.*?\shoes)/ 

或(可能更快)的grep每個字分開,然後array_intersect結果

2

這可能是一個非常天真的解決方案(很可能有更高效/優雅的解決方案),但我可能會做類似以下的事情:

$keywords = array(
    'great', 
    'leather', 
    'shoes' 
); 

$titles = array(
    'Get Some Really Great Leather Shoes', 
    'Leather Shoes Are Great', 
    'Great Day! Those Are Some Cool Leather Shoes!', 
    'Shoes, Made of Leather, Can Be Great', 
    'Leather Shoes on Sale Today!', 
    'You\'ll Love These Leather Shoes Greatly', 
    'Great Shoes Don\'t Come Cheap' 
); 

$matches = array(); 
foreach($titles as $title) 
{ 
    $wordsInTitle = preg_split('~\b(\W+\b)?~', $title, null, PREG_SPLIT_NO_EMPTY); 
    if(array_uintersect($keywords, $wordsInTitle, 'strcasecmp') == $keywords) 
    { 
    // we have a match 
    $matches[] = $title; 
    } 
} 

var_dump($matches); 

不知道這是如何的基準雖然。

1

你可以使用

/(?=.*?\great\b)(?=.*?\bshoes\b)(?=.*?\bleather\b)/ 

注意兩件事情

a)您需要兩端的單詞界限,否則最終可能會找到包含您正在尋找的單詞的單詞,例如「皮革帶來的巨大魅力」。

b)我使用懶惰通配符匹配(即*?)。這提高了效率,因爲默認情況下*是貪婪的(即,它消耗盡可能多的字符,因爲它可以匹配,並且只給予它們以支持整體匹配)。因此,如果我們沒有尾隨,。*將匹配該行中的所有內容,然後返回匹配「很好」。然後重複「鞋」和「皮革」的相同程序。通過使*懶惰,我們避免這些不必要的回溯。

+0

Jasmeet,看到我對RegExp的評論非常接近你的,這是Alan Moore的。看到我的評論從「Works on ...」開始。你有一個想法可能是什麼問題? – Volomike 2010-04-14 22:54:52

+1

@Volomike,我不太確定,尤其是因爲我甚至無法讓Alan Moore的正則表達式在Perl上編譯。我得到一個關於嵌套量詞的錯誤(一個量詞,如*,+被封在另一個分詞器中),這是爲了防止大量的回溯。我知道Alan正在使用possesive量詞,這使得正則表達式可以避免額外的回溯。但是perl仍然不喜歡它,並且鑑於Perl和PHP都使用基於NFA的正則表達式引擎,我懷疑你可能會遇到類似的問題。 – Jasmeet 2010-04-15 01:07:49

1

我不知道該絕對最快的方式,但是這可能是一個正則表達式來做到這一點的最快方法:

'#(?:\b(?>great\b()|leather\b()|shoes\b()|\w++\b)\W*+)++\1\2\3#i' 

此字符串中的每一個字相匹配,如果字恰好是你的關鍵字之一,空的捕獲組「檢查它」。一旦字符串中的所有單詞都匹配完畢,後向引用(\1\2\3)確保三個關鍵字中的每一個都至少出現過一次。

通常爲這類任務推薦的基於前瞻的方法需要多次掃描整個字符串 - 每個關鍵字一次。這個正則表達式只需掃描一次字符串 - 事實上,所有格量化符(++,*+)和原子組((?>...))禁用了回溯。

這就是說,我仍然會採用先行的方法,除非我知道它是造成瓶頸。在大多數情況下,其較大的可讀性值得在性能上取捨。

+0

哇,這太令人印象深刻了!不過,我會接受你的建議,並使用更易讀的方法,以便將來的程序員不會感到不安。 – Volomike 2010-04-14 06:29:43

+0

適用於多個1至3個單詞關鍵字短語。但是當我有$ KP的「無線電夜」時,一個$ RegExp的'#(?:\ b(?> radio \ b()| night \ b()| \ w ++ \ b)\ W * +)+ + \ 1 \ 2 \ 3#i'和$'媒體廣播和電視歷史'的標題,我收到錯誤「編譯失敗:引用不存在的子模式在偏移量48」。我可以用try/catch塊修復,但可能應該先修正RegExp錯誤,對不對? – Volomike 2010-04-14 18:39:51

+1

您在該正則表達式中只有兩個捕獲組,因此您需要除去'\ 3'。 – 2010-04-15 02:16:44