PHP array sorting on relevance

Viewed 104

I have a PHP text array, which holds values like "Blue Pencil, Blue Pen, Blue, Red Pencil, Red Ink, Red Pen, Blue Notebook, etc...."

I need to run through each array item, and show the matching results in order of matching RELEVANCE. Like, if the user searches for the term "Blue", then the 3rd item "Blue" which is a perfect match should get listed at the top, followed by 2nd item "Blue Pen", then by 1st "Blue Pencil" and finally by "Blue Notebook". Rest all non-Blue items will be discarded.

I tried using the sort and rsort functions on PHP arrays (both before and after pulling matching Blue items), but they simply sort based on alphabetical and reverse-alpha listing. There is no relevance match in there. Like using sort($array) returns the following

Blue
Blue Notebook
Blue Pen
Blue Pencil

which is NOT really as per the expected "relevant" result.

Also the levenshtein function does NOT fit, as it has a restriction that it works on strings with maximum length 255. My strings can be longer.

To draw a parallel, MySQL has this match-against clause which does the work.

SELECT * , MATCH (col1, col2) AGAINST ('some words' IN NATURAL LANGUAGE MODE)

Looking for something similar in PHP, if anyone can provide any pointers or any UDF to be written.

3 Answers

Fulltext search is a complex subject.

A combination of array_filter and usort with levenshtein will result in the answer you want for this particular query, but you will find that it quickly falls apart for other queries:


$data = explode(', ', 'Blue Pencil, Blue Pen, Blue, Red Pencil, Red Ink, Red Pen, Blue Notebook');
$query = 'Blue';

// Do an exact match first
$data = array_filter($data, fn ($s) => str_contains($s, $query));

// Sort by the Levenshtein distance from the $query
usort($data, fn($a, $b) => levenshtein($query, $a) - levenshtein($query, $b));

var_dump($data);

// Will print: 
// array(4) {
//    [0]=>
//   string(4) "Blue"
//   [1]=>
//   string(8) "Blue Pen"
//   [2]=>
//   string(11) "Blue Pencil"
//   [3]=>
//   string(13) "Blue Notebook"
// }

Think about:

  • What happens if a user uses different capitalization (exact match won't work)
  • What if a user is looking for "a blue notebook" (you'd need some kind of string tokenization)
  • Do you want to remove/ignore certain words? (such as "the", "a", etc.)
  • What happens if you have thousands of words to look through? This solution won't be very performant.

You may eventually find that you end up reaching for a true search engine, such as Apache Lucene or Elasticsearch.

$input = [ "Blue Pencil", "Blue Pen", "Blue", "Red Pencil", "Red Ink", "Red Pen", "Blue Notebook" ];

$result = preg_grep("/^blue/i", $input); print_r($result);

You need asort function

$data = array("Blue Pencil", "Blue Pen", "Blue", "Red Pencil", "Red Ink", "Red Pen", "Blue Notebook");
asort($data);
print_r($data);

Output

Array ( [2] => Blue [6] => Blue Notebook [1] => Blue Pen [0] => Blue Pencil [4] => Red Ink [5] => Red Pen [3] => Red Pencil )
Related