2010-04-13 3 views
5

Was ist der schnellste Weg in PHP, um eine Keyword-Liste zu nehmen und sie mit einem Suchergebnis (wie ein Array von Titeln) für alle Wörter?Schnellste PHP-Routine zum Vergleichen von Wörtern

Zum Beispiel, wenn mein Keyword Phrase ist "große Lederschuhe", dann sind die folgenden Titel wäre ein Spiel ...

  • Erhalten Sie einige wirklich Große Lederschuhe
  • Lederschuhe Sind Groß
  • Groß Tag! Das sind einige coole Lederschuhe!
  • Schuhe, Aus Leder, Can Be Große

... während diese wäre nicht ein Spiel:

  • Lederschuhe auf Sale Heute !
  • Sie finden diese Lederschuhe Greatly
  • Große Schuhe Liebe nicht billig

Kommen Sie sich vorstellen, ich einige Trick mit Array-Funktionen gibt es eine oder RegEx (Regular Expression) dies schnell zu erreichen .

+1

Ich würde eine Kombination aus explode, array_merge/array_unique und zählen für diese, aber ich kann nicht sagen, wie schnell es ist. – svens

Antwort

4

Ich würde einen Index für die Worte in dem Titel verwenden und testen, ob jeder Suchbegriff in diesem Index ist:

$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 für die Unicode-Unterstützung. –

1

Ich kann Ihnen keine definitive Antwort anbieten, aber ich würde versuchen, jede vorgeschlagene Lösung zu vergleichen und würde damit beginnen, einige in_array zusammen zu verketten.

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

können Sie preg_grep() Array gegen so etwas wie

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

oder (wahrscheinlich schneller) grep jeweils separat Wort und array_intersect dann die Ergebnisse

2

Es könnte eine ziemlich naive Lösung sein (wahrscheinlich gibt es effizientere/elegantere Lösungen), aber ich würde wahrscheinlich so etwas wie das folgende tun:

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

Keine Ahnung, wie diese Benchmarks jedoch.

1

Sie

verwenden könnten
/(?=.*?\great\b)(?=.*?\bshoes\b)(?=.*?\bleather\b)/ 

Hinweis ein paar Dinge

a) Sie brauchen Wortgrenzen an beiden Enden, sonst könnten Sie passende Wörter finden, die diejenigen enthalten, nach denen Sie suchen, zB "Schuhe aus Leder bringen Größe".

b) Ich verwende faule Wildcard-Übereinstimmung (d. H. *?). Dies verbessert die Effizienz, da * standardmäßig gierig ist (d. H. Es verbraucht so viele Zeichen wie es passt und gibt sie nur zugunsten einer Gesamtübereinstimmung auf). Also, wenn wir nicht die nachlaufende haben?,. * Wird alles in der Zeile übereinstimmen und dann zurückverfolgen, um "groß" zu entsprechen. Die gleiche Prozedur wird dann für "Schuhe" und "Leder" wiederholt. Indem wir * faul machen, vermeiden wir diese unnötigen Backtracks.

+0

Jasmeet, sehe meinen Kommentar zu einer RegExp ganz in deiner Nähe, die von Alan Moore stammt. Siehe meinen Kommentar beginnend mit "Works on ...". Hast du eine Idee, was das Problem sein könnte? – Volomike

+1

@Volomike, ich bin mir nicht ganz sicher, vor allem, da ich nicht einmal Alan Moores Regex auf Perl kompilieren kann. Ich bekomme einen Fehler über verschachtelte Quantifier (ein Quantifizierer wie *, + .., der in einem anderen Quatifier eingeschlossen ist), der da ist, um gegen massives Backtracking zu schützen. Ich weiß, dass Alan Possessive-Quantoren verwendet, die es der Regex ermöglichen, zusätzliche Backtracks zu vermeiden. Aber Perl mag es immer noch nicht, und da Perl und PHP NFA-basierte Regex-Engines verwenden, vermute ich, dass Sie ein ähnliches Problem haben. – Jasmeet

1

Ich weiß nicht, über den absoluten schnellsten Weg, aber das ist wahrscheinlich der schnellste Weg, um es mit einem regulären Ausdruck zu tun:

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

Dies jedes Wort in der Zeichenfolge übereinstimmt, und wenn das Wort zufällig eines Ihrer Keywords ist, wird die leere Erfassungsgruppe "abhaken". Sobald alle Wörter in der Zeichenfolge übereinstimmten, stellen die Rückverweise (\1\2\3) sicher, dass jedes der drei Schlüsselwörter mindestens einmal gesehen wurde.

Der Lookahead-basierte Ansatz, der normalerweise für diese Art von Aufgabe empfohlen wird, muss potenziell die gesamte Zeichenfolge mehrere Male scannen - einmal für jedes Schlüsselwort. Diese Regex muss die Zeichenkette nur einmal scannen - tatsächlich wird das Zurückverfolgen durch die Possessiv-Quantoren (++, *+) und Atomgruppen ((?>...)) deaktiviert.

Das sagte, ich würde immer noch mit dem Lookahead-Ansatz gehen, wenn ich nicht wusste, dass es einen Flaschenhals verursacht. In den meisten Fällen ist die größere Lesbarkeit der Leistung wert.

+0

Wow, das ist sehr beeindruckend! Ich werde jedoch Ihren Rat beherzigen und mit dem besser lesbaren Programm fortfahren, damit sich zukünftige Programmierer nicht aufregen. – Volomike

+0

Funktioniert mit mehreren Keyword-Phrasen mit 1 bis 3 Wörtern. Aber wenn ich ein $ KP von "Radio-Nacht" hatte, ein $ RegExp von '# (?: \ B (?> Radio \ b() | Nacht \ b() | \ w ++ \ b) \ W * +) + + \ 1 \ 2 \ 3 # i ', und ein $ Title von' Geschichte der Medien Radio und Fernsehen ', erhielt ich den Fehler "Compilation failed: Verweis auf nicht existierende Untermuster bei Offset 48". Ich kann das mit einem try/catch-Block beheben, aber sollte wahrscheinlich zuerst den RegExp-Bug beheben, richtig? – Volomike

+1

Sie haben nur zwei einfangende Gruppen in diesem Regex, also müssen Sie das '\ 3' loswerden. –