Я пытаюсь найти почти одинаковые значения в наборе полей, чтобы администратор мог их очистить.
Есть два критерия, по которым я соответствую
- Одна строка целиком содержится в другой и составляет не менее 1/4 своей длины
- Строки имеют расстояние редактирования менее 5% от общей длины двух строк
Псевдо-PHP код:
foreach($values as $value){
$matches = array();
foreach($values as $match){
if(
(
$value['length'] < $match['length']
&&
$value['length'] * 4 > $match['length']
&&
stripos($match['value'], $value['value']) !== false
)
||
(
$match['length'] < $value['length']
&&
$match['length'] * 4 > $value['length']
&&
stripos($value['value'], $match['value']) !== false
)
||
(
abs($value['length'] - $match['length']) * 20 < ($value['length'] + $match['length'])
&&
0 < ($match['changes'] = levenshtein($value['value'], $match['value']))
&&
$match['changes'] * 20 <= ($value['length'] + $match['length'])
)
){
$matches[] = &$match;
}
}
// output matches for current outer loop value
}
Я пытался сократить вызовы сравнительно дорогих функций stripos
и levenshtein
, где это возможно, что значительно сократило время выполнения.
Однако, как операция O (n ^ 2), она просто не масштабируется до больших наборов значений, и кажется, что значительная часть времени обработки тратится на итерацию массивов.
Некоторые свойства нескольких наборов значений, работающих с
Total | Strings | # of matches per string | |
Strings | With Matches | Average | Median | Max | Time (s) |
--------+--------------+---------+--------+------+----------+
844 | 413 | 1.8 | 1 | 58 | 140 |
593 | 156 | 1.2 | 1 | 5 | 62 |
272 | 168 | 3.2 | 2 | 26 | 10 |
157 | 47 | 1.5 | 1 | 4 | 3.2 |
106 | 48 | 1.8 | 1 | 8 | 1.3 |
62 | 47 | 2.9 | 2 | 16 | 0.4 |
Могу ли я сделать что-то еще, чтобы сократить время проверки критериев, и, что более важно, есть ли у меня способы уменьшить количество требуемых проверок критериев (например, путем предварительной обработки входных значений), так как есть такая низкая селективность?
Редактировать: Внедренное решение
// $values is ordered from shortest to longest string length
$values_count = count($values); // saves a ton of time, especially on linux
for($vid = 0; $vid < $values_count; $vid++){
for($mid = $vid+1; $mid < $values_count; $mid++){ // only check against longer strings
if(
(
$value['length'] * 4 > $match['length']
&&
stripos($match['value'], $value['value']) !== false
)
||
(
($match['length'] - $value['length']) * 20 < ($value['length'] + $match['length'])
&&
0 < ($changes = levenshtein($value['value'], $match['value']))
&&
$changes * 20 <= ($value['length'] + $match['length'])
)
){
// store match in both directions
$matches[$vid][$mid] = true;
$matches[$mid][$vid] = true;
}
}
}
// Sort outer array of matches alphabetically with uksort()
foreach($matches as $vid => $mids){
// sort inner array of matches by usage count with uksort()
// output matches
}