2017-05-19 139 views
0

我試圖爲我的網站構建這個標記系統,在那裏它檢查寫入的文章(可能是400-1000個單詞),對於特定的單詞和使用所有找到的關鍵字,從陣列。帶有preg_match和foreach的PHP標記系統

我所做的是工作正常,但有一些問題我想解決。

$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]; 

首先,我想知道,如果有什麼辦法,我可以使這個更有效。我有一個包含大約5.000個關鍵字的數據庫,並且每天都在擴展。

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

我希望它只是與所有匹配的字符串 - 但只有一次匹配。如果蘋果被提及5次,那麼只有1個應該在字符串中。

一說 - 這工程。但我不覺得,這是最好的解決方案。

編輯:

我現在想,但我不能讓它在所有的工作。

$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; 
+0

它爲什麼會運行250萬次?它只對每個$ target檢查$ a,它只會運行count($ targets)次。 – Devon

+1

如果您的文章有400-1000字,那麼您應該首先反駁。沒有在文章中找到標籤,而是從標籤中的文章中找到單詞。 (這將是更有效的5-10倍)。有了這個解決方案,你也可以先過濾短的單詞(a,an,the,is ....)並且不要搜索它們。 –

+1

由於您只在找到目標單詞時才添加條目到'$ b'中,您可以使用'echo implode(',',$ b);'來顯示您找到的單詞。 – roberto06

回答

0

preg_match已經保存了匹配。所以:

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

3參數是已經保存的比賽,改變這個:

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

要這樣:

$matches = []; 
preg_match("/\b" . $t . "\b/i", $a, $matches); 
$b = array_merge($b, $matches); 

但是,如果你是直接比較的話,該文件推薦使用strpos()

提示
不要使用的preg_match(),如果你只是想檢查一個字符串包含在另一個字符串。使用strpos(),因爲它會更快。


編輯

你可以提高(性能)你的代碼,如果你仍然想這樣做是爲了使用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|(管道),所以你的正則表達式是這樣的:(target1|target2|target3|targetN)所以你只做一個搜索,而不是那個foreach。

+0

謝謝。我知道strpos()更快,但不區分大小寫。 stripos是,但stripos的事情是,它不能找到完全匹配。 Applecider f.ex.我想盡可能快地完成它,但strpos()和stripos()不起作用,因爲提到了兩個因素。 – Morten

+0

@Morten好吧,看看我的編輯 – matiaslauriti

+0

@Morten你可以用關鍵字的空格來查找帶有前綴和後綴的stripos,例如查找「Apple」。它是一個精確的單詞匹配。 – Code3d

1

首先,我想提供一個非正則表達式方法,然後我會進入一些冗長的正則表達式考慮。

因爲你搜索 「針」 是整個單詞,你可以利用的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的回答可能會讓你a正確的結果,它提供很少的嘗試,以提供任何大的效益收益。在一個循環

  1. 不要使用preg_match()preg_match_all()是專門設計來捕捉多次出現在一個單一的呼叫:

    我會提出兩點。 (將在後面的答案提供的代碼)

  2. 凝聚你的邏輯模式儘可能...

比方說,你有一個這樣的輸入:

$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   | 
--------------------------------------------------------------- 

有了這樣一個簡單的改進,從59,如果你只檢查$input這兩個標籤,操作步驟需要滴:您可以通過合併這樣的草莓和草莓行更新表數據至。

因爲您正在處理> 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

已找到標籤array(0 =>'apple',1 =>'pear',2 => 'strawberry',3 =>'apples',4 =>'pear',5 =>'strawberries', )


......如果你已經設法閱讀了這篇文章,你可能會遇到類似於OP的問題,並且你想繼續前進而沒有遺憾/錯誤。請參閱my related Code Review post瞭解更多關於附帶案例考慮事項和方法邏輯的信息。

+0

@Morten我今天收到了我的答案upvote,並認爲我會重新訪問我的舊帖子。我能夠糾正我之前的模式(他們都缺少字邊界字符中的非捕獲組,我還添加了一個非正則表達式方法,可能證明效率更高(非正則表達式通常比正則表達式更快,但是您必須用你的實際項目數據來測試這個斷言)如果你有任何疑問或擔心,請問。ps我的舊正則表達式方法沒有說明,但你需要「double'array_flip()'」輸出數組和implode用逗號。 – mickmackusa