Задать вопрос
Adamos
@Adamos

Как бы упростить непростое сравнение строк?

Есть вот такая строчка: '0012002121210212002120002101210....'
Есть таблица в БД с такими же строчками, их длина в точности одинакова.
Задача: для этой новой строки найти в базе все строки, для которых в том месте, где в одной из строк 0, достаточно часто (95%) встречается 0 в другой строке. Места, где в обеих строках не 0, не в учет.
Собственно, код на пыхе - тривиальнейший:
$total = 0;
$match = 0;
for($n = 0; $n < $size; ++$n) {
    $c1 = $cur[$n];
    $c2 = $other[$n];
    if($c1 === '0' || $c2 === '0') {
        ++$total;
        if($c1 === $c2) {
            ++$match;
        }
    }
}
$percent = ($total > 0)? round($match * 100 / $total) : 0;
if($percent >= 95) ...

И все бы было хорошо, но умножаем время такого сравнения на количество строк в базе, да на количество новых строк, если она не одна... получается долговато. Пока не критично, но база растет, записей уже десятки тысяч.
Может быть, я упускаю возможность предварительной (или непосредственной) оптимизации?
  • Вопрос задан
  • 476 просмотров
Подписаться 2 Простой 26 комментариев
Решения вопроса 2
wataru
@wataru Куратор тега Алгоритмы
Разработчик на С++, экс-олимпиадник.
Фактически у вас тут запросы, "найти строки где много нулей на этих произвольных позициях". Никакого предподсчета я тут не вижу. Можно здорово ускорить, если хранить от строк только битовые последовательности (где '0' заменяется на единичный бит). Далее каждую строку надо про AND-ить побитово с новой и подсчитать количество единичных бит. Это теооетически раз в 64-100 ускорит вычисления, если 64-битный целый тип использовать. Или еще несколько раз сверху, если использовать векторизацию. Но как это в php сделать, я не знаю.

Даже без 5% ошибок оно не оптимизируется предподсчетами. Тупо найти строки, в которых '0' вот на этих позициях - особо не наоптимизируешь.
Ответ написан
GavriKos
@GavriKos
Ну из непосредственных.
Можно заранее для каждой пары строк вычислить нужный процент, и как только его превысили - сразу выходить из цикла сравнения посимвольного. Причем даже не процент, а количество совпадений. Это может дать прирост в ряде случаев.

Ну из очевидных еще оптимизаций - заранее в базе посчитать количество нулей в каждой строке. И вообще не пытаться проходить по тем строкам, в которых количество нулей меньше допустимого процента (но именно заранее - на этапе поиска подсчет нулей не даст оптимизацию).

Можно еще подумать насчет оптимизации хранимых строк. Например, заменить ненули на количество пропусков цикла. Т.е. число 0011100 записать как 00300, и уже при итерировании учитывать эту тройку как +=3.

Это так, что быстро в голову пришло
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
@res2001
Developer, ex-admin
Если в отдельной таблице базы хранить еще и предварительно посчитанные количества нулей для каждой позиции во всех строках, то задача сократится до 1 прохода по символам новой строки.
При добавлении/удалении строк, надо будет модифицировать и таблицу с количеством нулей.
Ответ написан
Adamos
@Adamos Автор вопроса
Попробовал составить бенчмарк для сравнения текущего... эм... сравнения с предложенными методиками.
Данные - тысяча строк по тысяче символов. По сравнению с исходными данными в строках 2 заменены на 1, чтобы было более бинарно, но это все равно строки. Каждая строка сравнивается с каждой, кроме себя, итого почти миллион итераций.
Старый код, который в вопросе, крутил эти данные 10,3 секунды.
Новый код - 0,5 секунды:
$h = gmp_hamdist(
    $firstGmp, // gmp_init первой строки вынесен за цикл
    gmp_init($other, 2)
);
$sum = $zeroes[$n1] + $zeroes[$n2]; // подсчитанные до цикла количества 0 в строках
$percent = round(($sum - $h) * 100 / ($sum + $h));
if ($percent >= 95) { ...

Результаты идентичны. Собственно, только две строки из этой тысячи совпали на 96%, остальные - менее.
Проверка, можно ли исключить из сравнения строки по общему количеству нулей, показала, что отсеивается менее 3%. Такие данные, да...

Благодарю поучаствовавших в дискуссии и отдельно Stalker_RED - за практически готовое решение, причем "малой кровью".
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы