Нечёткий поиск. Bitap алгоритм, модификация от Wu-Manber

Временами нужен нечёткий поиск в тексте, но не всегда можно использовать внешние компоненты. Данный алгоритм прост, достаточно быстр.

Подробное описание  алгоритма здесь. Для эмуляции битовых операций использовалась эта публикация. Код можно выполнять и на сервере и на клиенте. Попутно вычисляет расстояние Левенштейна.

Использовалась эмуляция, а не сами битовые операции, так как эмуляция может работать на гораздо бОльших текстах и гораздо быстрее.

функция ПобитовоеУмножение(Число1,Число2,ФорматнаяСтрока)
возврат СтрЗаменить(СтрЗаменить(Формат(Число(Число1) + Число(Число2), ФорматнаяСтрока), "1", "0"), "2", "1");
КонецФункции

функция ПобитовоеСложение(Число1,Число2,ФорматнаяСтрока)
возврат СтрЗаменить(Формат(Число(Число1) + Число(Число2), ФорматнаяСтрока), "2", "1");
КонецФункции

функция СдвигВПраво(Число1,n)
возврат "1" + лев(Число1,n-1);
КонецФункции

функция АлгоритмНечеткогоПоиска(Текст,СтрокаПоиска) экспорт
m = СтрДлина(Текст);
n = СтрДлина(СтрокаПоиска);
//для формирования нулевого битового вектора нужной длины
ФорматнаяСтрока = "ЧЦ="+формат(n,"ЧГ=0")+"; ЧН=; ЧВН=; ЧГ=0";
//заполнение вспомогательных битовых векторов на каждый символ строки поиска
U = новый Соответствие;
для i = 1 по n цикл
ключ = сред(СтрокаПоиска,i,1);
БитовыйВектор = U.Получить(ключ);
если БитовыйВектор = Неопределено тогда
БитовыйВектор = формат(0,ФорматнаяСтрока);
КонецЕсли;
БитовыйВектор = лев(БитовыйВектор,i-1) + "1"+прав(БитовыйВектор,n-i);
U.Вставить(ключ,БитовыйВектор);
КонецЦикла;
МассивНоваяПопытка = новый Массив(m);
//макс расстояние Левенштейна
МаксОшибка = макс(2,цел(СтрДлина(СтрокаПоиска)/10));
Для Ошибка = 0 по МаксОшибка цикл
M1 = формат(0,ФорматнаяСтрока);
МассивПредыдущаяПопытка = новый массив;
Для каждого стр из МассивНоваяПопытка цикл
МассивПредыдущаяПопытка.Добавить(стр);
КонецЦикла;
для j = 1 по m цикл
БитовыйВектор = U.Получить(сред(Текст,j,1));
Если БитовыйВектор = Неопределено тогда
БитовыйВектор = формат(0,ФорматнаяСтрока);
КонецЕсли;
УмножениеСВектором = ПобитовоеУмножение(СдвигВПраво(M1,n),БитовыйВектор,ФорматнаяСтрока);
Если Ошибка = 0 тогда
ПредыдущаяПопыткаj_1 = формат(0,ФорматнаяСтрока);
иначеЕсли j = 1 тогда
ПредыдущаяПопыткаj_1 = формат(0,ФорматнаяСтрока);
иначе
ПредыдущаяПопыткаj_1 = МассивПредыдущаяПопытка[j-2];
КонецЕсли;

Если Ошибка = 0 тогда
ПредыдущаяПопыткаj = формат(0,ФорматнаяСтрока);
иначе
ПредыдущаяПопыткаj = МассивПредыдущаяПопытка[j-1];
КонецЕсли;

вставкаM1 = ПобитовоеСложение(УмножениеСВектором,ПредыдущаяПопыткаj_1,ФорматнаяСтрока);
УдалениеM1 = ПобитовоеСложение(УмножениеСВектором,СдвигВПраво(ПредыдущаяПопыткаj,n),ФорматнаяСтрока);
ЗаменаM1 = ПобитовоеСложение(УмножениеСВектором,СдвигВПраво(ПредыдущаяПопыткаj_1,n),ФорматнаяСтрока);
//результат = вставкаM1 или УдалениеM1 или ЗаменаM1
M1 = ПобитовоеСложение(вставкаM1,УдалениеM1,ФорматнаяСтрока);
M1 = ПобитовоеСложение(M1,ЗаменаM1,ФорматнаяСтрока);
МассивНоваяПопытка[j-1] = M1;
Если прав(M1,1)="1" тогда
//найденная позиция - последний символ подстроки в тесте
//так как длина подстроки и строки поиска может быть разной,
//то ищем где первые ошибка+1 бит = 1, остальные 0
СтрокаДляСравнения = формат(0,"ЧЦ="+формат(Ошибка+1,"ЧГ=0")+"; ЧН=; ЧВН=; ЧГ=0") ;
СтрокаДляСравнения = СтрЗаменить(СтрокаДляСравнения,"0","1");
СтрокаДляСравнения = СтрокаДляСравнения + формат(0,"ЧЦ="+формат(n-Ошибка-1,"ЧГ=0")+"; ЧН=; ЧВН=; ЧГ=0");
Для Инт = -(j-1) по 0 цикл
Если МассивНоваяПопытка[-Инт] = СтрокаДляСравнения тогда
Прервать;
КонецЕсли;
КонецЦикла;
возврат(новый Структура("Слово,позиция,ошибок",Сред(Текст,-Инт+1,j+Инт),-Инт+1,Ошибка));
КонецЕсли;
КонецЦикла;
КонецЦикла;
возврат(новый Структура("Слово,позиция,ошибок","",0,0));
КонецФункции

 

10 Comments

  1. user-z99999

    Как насчет скорости работы?

    Проверяли, например на 1 млн.записей?

    Reply
  2. trim89

    (1) Причем тут записи, если это поиск в тексте? Естественно циклом проходить записи и смотреть по каждой — это жутко долго. Этот алгоритм без индексации, в такой задаче его нерационально использовать.

    По скорости. Взял текст 2000 символов, искал слово 13 символов, с 2 ошибкам. Находит за 3 секунды. Естественно, искомое слово было в самом конце текста, так что это максимальное время.

    Reply
  3. MaxxiMiliSan

    1с очень медленно работает с текстом. возможно нужно было оформить это в качестве вк.

    Reply
  4. trim89

    (3) Ключевое, что не всегда удобно вк использовать. Да и зачем своё городить, когда уже готовые есть.

    Reply
  5. MaxxiMiliSan

    (4) Подскажите, правильно ли я понимаю, что поиск только для цифр?

    Reply
  6. YanTsys
    на гораздо бОльших текстах

    Для неграмотных?

    Reply
  7. mikl79

    спасибо, работает

    Reply
  8. trim89

    (5)нет, не только

    Reply
  9. BackinSoda

    Без примера поиска не разобраться

    Reply
  10. trim89

    (9) На просторах интернета есть подробные описания, для понятия самой идеи вполне достаточно.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *