2010-04-13 11 views
5

Qual è il modo più veloce in PHP per prendere un elenco di parole chiave e abbinarlo a un risultato di ricerca (come una serie di titoli) per tutte le parole?La routine PHP più veloce per abbinare le parole

Per esempio, se la mia frase chiave è "grandi scarpe di cuoio", quindi i seguenti titoli sarebbe un match ...

  • ottenere qualche Davvero Grandi scarpe di cuoio
  • Scarpe in pelle Are Great
  • Grande Giorno! The Are Some Cool Scarpe in pelle!
  • Scarpe, fatti di pelle , può essere Grande

... mentre questi non sarebbe partner:

  • Scarpe in pelle in vendita da oggi !
  • vi innamorerete di queste scarpe di cuoio Notevolmente
  • Great Shoes non sono a buon mercato

Immagino che ci sia qualche trucco con funzioni di array o una RegEx (Regular Expression) per raggiungere questo obiettivo rapidamente .

+1

Vorrei usare una combinazione di explode, array_merge/array_unique e contare per questo, ma non posso dire quanto sia veloce. – svens

risposta

4

vorrei utilizzare un indice per le parole nei titoli e prova se ogni termine di ricerca è in tale indice:

$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

+1 per il supporto Unicode. –

1

Non posso offrirti una risposta definitiva, ma proverei a valutare ogni soluzione suggerita e inizierei a concatenare insieme alcuni in_array.

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

è possibile preg_grep() la matrice contro qualcosa come

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

o (probabilmente più veloce) grep ogni parola separatamente e poi array_intersect i risultati

2

Potrebbe essere una soluzione abbastanza ingenua (molto probabilmente ci sono soluzioni più efficienti/eleganti), ma probabilmente farei qualcosa di simile al seguente:

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

Non ho idea di come questo benchmark sia.

1

si potrebbe usare

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

nota un paio di cose

a) Hai bisogno di limiti di parole ad entrambe le estremità altrimenti potresti finire con parole che contengono quelle che stai cercando, ad esempio "scarpe di cuoio portano grandezza".

b) Utilizzo la corrispondenza con caratteri jolly pigri (ad es. *?). Ciò migliora l'efficienza, poiché per impostazione predefinita * è goloso (vale a dire consuma tutti i caratteri che può abbinare e li abbandona solo a favore di una corrispondenza generale). Quindi se non abbiamo il trailing?,. * Corrisponderà a tutto nella riga e quindi tornerà indietro per corrispondere a 'grande'. La stessa procedura viene poi ripetuta per "scarpe" e "pelle". Facendo * pigro, evitiamo questi backtracks inutili.

+0

Jasmeet, guarda il mio commento su un RegExp molto vicino al tuo, che era di Alan Moore. Vedi il mio commento che inizia con "Funziona su ...". Hai un'idea di quale potrebbe essere il problema? – Volomike

+1

@Volomike, non ne sono del tutto sicuro, soprattutto perché non riesco nemmeno a ottenere la regex di Alan Moore da compilare su Perl. Ottengo un errore sui quantificatori nidificati (un quantificatore come *, + .. è racchiuso in un altro quatificatore), che è lì per proteggere contro un massiccio backtracking. So che Alan sta usando quantificatori possesivi, il che consente alla regex di evitare backtracks extra. Ma perl non mi piace ancora, e dato che sia Perl che PHP usano motori regex basati su NFA, sospetto che potresti incontrare un problema simile. – Jasmeet

1

Non so circa il modo più veloceassoluto, ma questo è probabilmente il modo più veloce per farlo con una regex:

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

Questo corrisponde ogni parola nella stringa, e se la parola sembra essere una delle tue parole chiave, il gruppo di acquisizione vuoto "lo controlla". Una volta che tutte le parole nella stringa sono state abbinate, i riferimenti posteriori (\1\2\3) assicurano che ciascuna delle tre parole chiave sia stata vista almeno una volta.

L'approccio basato su lookahead, generalmente consigliato per questo tipo di attività, deve eseguire potenzialmente la scansione dell'intera stringa più volte, una per ogni parola chiave. Questa regex deve solo eseguire la scansione della stringa una volta, infatti, il backtracking è disabilitato dai quantificatori possessivi (++,) e gruppi atomici ((?>...)).

Detto questo, vorrei ancora andare con l'approccio lookahead a meno che non sapessi che stava causando un collo di bottiglia. Nella maggior parte dei casi, la sua maggiore leggibilità vale il compromesso in termini di prestazioni.

+0

Wow, questo è estremamente impressionante! Prenderò il tuo consiglio, però, e seguirò quello più leggibile in modo che i futuri programmatori non si arrabbino. – Volomike

+0

Funziona su più frasi parola chiave da 1 a 3 parole. Ma quando ho avuto un $ KP di "radio night", un $ RegExp di "# (?: \ B (?> Radio \ b() | night \ b() | \ w ++ \ b) \ W * +) + + \ 1 \ 2 \ 3 # i ', e un $ Titolo di' storia dei media radio e televisione ', ho ricevuto l'errore "Compilazione fallita: riferimento a subpattern inesistente all'offset 48". Posso risolvere con un blocco try/catch, ma probabilmente dovrei risolvere prima il bug RegExp, giusto? – Volomike

+1

Hai solo due gruppi di cattura nell'espressione regolare, quindi devi liberarti di '\ 3'. –

Problemi correlati