Функция Русский MetaPhone для 1С:Предприятия любой платформы и конфигурации

Опубликовал Сергей Ожерельев (Поручик) в раздел Программирование - Практика программирования

Функция полезна для организации поиска информации в базе данных по нечёткому совпадению.

Данная функция была портирована с Visual Basic несколько лет назад, в ходе решения задачи по поиску контрагентов или физлиц (уже не помню) по нечёткому совпадению.
Обнаружил случайно, копаясь в старых работах на платформе 7.7, и решил выложить в надежде, что кому-нибудь пригодится.  Хочу заметить, что для платформы 8.1 может быть неактуально, в связи со штатными возможностями нечёткого поиска.

Оригинал функции и статья по теме находились по адресу «Как ваша фамилия», или Русский MetaPhone (сейчас там error 404, выложил здесь)

//________________________________________________________________________
//
// ** MetaPhone() algorithm **
// ** coded by Sergey (aka Porutchik) * 2006 http://forum.aeroion.ru
//
// based on description :
// © Peter Kankowski, 2002
// http://www.kankowski.narod.ru/dev/metaphoneru.htm
//
Функция  MetaPhoneRu(Знач W) Экспорт
   
//Заменяет ЙО, ЙЕ и др.; неплохо оптимизирован.

    //alf - алфавит кроме исключаемых букв, cns1 и cns2 - звонкие и глухие
    //согласные, cns3 - согласные, перед которыми звонкие оглушаются,
    //  ch, ct - образец и замена гласных
   
alf = "ОЕАИУЭЮЯПСТРКЛМНБВГДЖЗЙФХЦЧШЩЁЫ";
   
cns1 = "БЗДВГ";
   
cns2 = "ПСТФК";
   
cns3 = "ПСТКБВГДЖЗФХЦЧШЩ";
   
ch = "ОЮЕЭЯЁЫ";
   
ct = "АУИИАИА";
   
//S, V - промежуточные строки, i - счётчик цикла, B - позиция
    //найденного элемента, c$ - текущий символ, c_old$ - предыдущий символ
   
S= "";
   
V= "";
   
i= 0;
   
B= 0;
   
c= "";
   
old_c = "";

   
//Переводим в верхний регистр, оставляем только
    //символы из alf и копируем в S:
   
W = Врег(W);
    Для
i = 1 По СтрДлина(W) Цикл
       
c = Сред(W, i, 1);
        Если
Найти(alf, c) > 0 Тогда
           
S = S + c;
        КонецЕсли;
    КонецЦикла;
    Если
СтрДлина(S) = 0 Тогда
        Возврат
"";
    КонецЕсли;

   
//Сжимаем окончания:
   
Врем = Прав(S, 6);
    Если
Врем = "ОВСКИЙ"  Тогда
       
S = Лев(S, СтрДлина(S) - 6) + "@";
    ИначеЕсли
Врем = "ЕВСКИЙ" Тогда
       
S = Лев(S, СтрДлина(S) - 6) + "#";
    ИначеЕсли
Врем = "ОВСКАЯ" Тогда
       
S = Лев(S, СтрДлина(S) - 6) + "$";
    ИначеЕсли
Врем = "ЕВСКАЯ" Тогда
       
S = Лев(S, СтрДлина(S) - 6) + "%";
    Иначе
       
Врем = Прав(S, 4);
        Если (
Врем = "ИЕВА") ИЛИ (Врем = "ЕЕВА") Тогда
           
S = Лев(S, СтрДлина(S) - 4) + "9";
        ИначеЕсли (
Врем = "ОВНА") ИЛИ (Врем = "ЕВНА") Тогда
           
S = Лев(S, СтрДлина(S) - 4) + "8";
        ИначеЕсли (
Врем = "ОВИЧ") ИЛИ (Врем = "ЕВИЧ") Тогда
           
S = Лев(S, СтрДлина(S) - 4) + "7";
        Иначе
           
Врем = Прав(S, 3);
            Если (
Врем = "ОВА") ИЛИ (Врем = "ЕВА") Тогда
               
S = Лев(S, СтрДлина(S) - 3) + "9";
            ИначеЕсли
Врем = "ИНА" Тогда
               
S = Лев(S, СтрДлина(S) - 3) + "1";
            ИначеЕсли (
Врем = "ИЕВ") ИЛИ (Врем = "ЕЕВ") Тогда
               
S = Лев(S, СтрДлина(S) - 3) + "4";
            ИначеЕсли
Врем = "НКО" Тогда
               
S = Лев(S, СтрДлина(S) - 3) + "3";
            Иначе
               
Врем = Прав(S, 2);
                Если (
Врем = "ОВ") ИЛИ (Врем = "ЕВ") Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "4";
                ИначеЕсли
Врем = "АЯ" Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "6";
                ИначеЕсли (
Врем = "ИЙ") ИЛИ (Врем = "ЫЙ") Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "7";
                ИначеЕсли (
Врем = "ЫХ") ИЛИ (Врем = "ИХ") Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "5";
                ИначеЕсли (
Врем = "ИН") Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "8";
                ИначеЕсли (
Врем = "ИК") ИЛИ (Врем = "ЕК") Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "2";
                ИначеЕсли (
Врем = "УК") ИЛИ (Врем = "ЮК") Тогда
                   
S = Лев(S, СтрДлина(S) - 2) + "0";
                КонецЕсли;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;

   
//Оглушаем последний символ, если он - звонкий согласный:
   
B = Найти(cns1, Прав(S, 1));
    Если
B > 0 Тогда
       
S = Сред(S, СтрДлина(S)-1, 1);
       
S = S + Сред(cns2, B, 1);
    КонецЕсли;
   
old_c = " ";
   
//Основной цикл:
   
Для i = 1 По СтрДлина(S) Цикл
       
c = Сред(S, i, 1);
       
B = Найти(ch, c);
        Если
B > 0 Тогда //Если гласная
           
Если (old_c = "Й") ИЛИ (old_c = "И") Тогда
                Если (
c = "О") ИЛИ (c = "Е") Тогда //Буквосочетания с гласной
                   
old_c = "И";
                   
V = Сред(V, СтрДлина(V)-1, 1);
                   
V = V + old_c;
                Иначе
//Если не буквосочетания с гласной, а просто гласная
                   
Если c <> old_c Тогда
                       
V = V + Сред(ct, B, 1);
                    КонецЕсли;
                КонецЕсли;
            Иначе
//Если не буквосочетания с гласной, а просто гласная
               
Если c <> old_c Тогда
                       
V = V + Сред(ct, B, 1);
                КонецЕсли;
            КонецЕсли;
        Иначе
//Если согласная
           
Если c <> old_c Тогда //для «Аввакумов»
               
Если Найти(cns3, c) > 0 Тогда //Оглушение согласных
                   
B = Найти(cns1, old_c);
                КонецЕсли;
                Если
B > 0 Тогда
                   
old_c = Сред(cns2, B, 1);
                   
V = Сред(V, СтрДлина(V)-1, 1);
                   
V = V + old_c;
                КонецЕсли;
                Если
c <> old_c Тогда
                   
V = V + c; //для «Шмидт»
               
КонецЕсли;
            КонецЕсли;
        КонецЕсли;
       
old_c = c;
    КонецЦикла;
    Возврат
V;
КонецФункции
// ** MetaPhoneRu() algorithm **

См. также

Комментарии
1. Александр Рытов (Арчибальд) 2655 27.09.09 10:41 Сейчас в теме
2. Евгений Мартыненков (JohnyDeath) 290 28.09.09 09:57 Сейчас в теме
3. Сергей Ожерельев (Поручик) 3536 28.09.09 11:08 Сейчас в теме
(2) если вы про это http://infostart.ru/public/15765/, но я никогда не был любителем ВК.
4. Евгений Мартыненков (JohnyDeath) 290 28.09.09 11:32 Сейчас в теме
(3) я про ВК. по ссылке выше она используется. Также она используется не только мной. Все очень довольны. Если же нет желания использовать ВК, тогда ДА
5. Сергей Ожерельев (Поручик) 3536 28.09.09 11:38 Сейчас в теме
(4)
>>> Также она используется не только мной.
Знавал её ещё с клюшечных времён, так как тоже один или пару раз воспользовался, но потом по каким-то причинам отказался и забыл напрочь.
6. Sk0rp Моя (Sk0rp) 100 21.10.09 11:36 Сейчас в теме
StrMatch использует алгоритмы MetaPhone
7. Сергей Ожерельев (Поручик) 3536 11.05.10 21:07 Сейчас в теме
К сожалению, ссылка http://www.kankowski.narod.ru/dev/metaphoneru.htm и сам сайт благополучно скончались. А жаль. Многие материалы там до сих пор были актуальными.
Есть мысль реанимировать статью «Как ваша фамилия», или Русский MetaPhone, сохранилась на диске. Стоит ли?
8. Михаил Агальцов (mikeA) 13.05.10 14:11 Сейчас в теме
(7) Выкладывай, интересно будет прочитать.
Может быть есть ещё что-то на тему нечёткого поиска для ФИО?
9. Сергей Ожерельев (Поручик) 3536 13.05.10 14:16 Сейчас в теме
(8) Ну вот один отозвался. Хорошо, сегодня вывалю на своём сайте, да простит меня автор статьи.
10. Сергей Ожерельев (Поручик) 3536 13.05.10 15:20 Сейчас в теме
11. Сергей Ожерельев (Поручик) 3536 14.05.10 00:39 Сейчас в теме
(8)
Ещё одна прикольная штука на тему нечёткого поиска. :D
Soundex на 1С: Предприятии

Сразу предупреждаю, тесты показали неожиданные результаты.
12. Михаил Агальцов (mikeA) 14.05.10 14:42 Сейчас в теме
Вот тогда до кучи функция для вычисления расстояния Левенштейна.
Взято отсюда.
Вроде бы больше похоже на правду - для Иванов и Иваноф метафон даёт разницу в два последних символа, расстояние Левенштейна единица.
Теперь осталось проверить как будет работать СписокПолнотекстовогоПоиска :)
Прикрепленные файлы:
levenshtein.txt
Поручик; +1 Ответить 1
13. Сергей Ожерельев (Поручик) 3536 14.05.10 17:47 Сейчас в теме
(12) Молодец!
О расстоянии Левенштейна думал раньше, руки не дошли.
Можно опубликую у себя? Только английские термины заменю на русские. Так гламурнее будет.

Кстати, ещё есть similar_text
http://ru2.php.net/manual/en/function.similar-text.php
Где бы найти описание алгоритма Оливера? Перетряхнул гугл, ничего внятного.
14. Сергей Ожерельев (Поручик) 3536 14.05.10 18:18 Сейчас в теме
>>> Иванов и Иваноф, Васильев и Васильефф

Я бы сказал, логично что метафон в реализации Каньковского даёт различия, всё-таки окончание -оф, -еф для русских фамилий нехарактерно, скорее так напишут за бугром, причём с двумя -фф.
Хотя можно попробовать подправить алгоритм

[FIND]
Если (Врем = "ОВ") ИЛИ (Врем = "ЕВ") Тогда

[REPLACE WITH]

Если (Врем = "ОВ") ИЛИ (Врем = "ЕВ") ИЛИ (Врем = "ОФ")  ИЛИ (Врем = "ЕФ") Тогда
...Показать Скрыть
15. Артур Аюханов (artbear) 859 15.05.10 07:19 Сейчас в теме
Данную задачу было бы интересно покрыть тестами для разных вариантов фамилий и звуковых сочетаний.
ИМХО это совсем несложно

Кстати, для имен, не фамилий, алгоритмы нормально работают?
16. Сергей Ожерельев (Поручик) 3536 15.05.10 16:00 Сейчас в теме
(15)
Были у меня подобные тесты в 2006 г, когда сливал данные из базы бухгалтерии и ИНЭК-Страховщика в одну на 1С Страхование или что-то в этом роде. Базы были примерно тысяч по 10 записей физлиц. Насколько помню, процент попадания для разных вариантов составил 90-95.
17. Михаил Агальцов (mikeA) 18.05.10 14:52 Сейчас в теме
(13)
> Можно опубликую у себя?
Да без проблем.

Вот ещё материал: Sam's String Metrics - библиотека java, больше десятка подобных функций, с описанием.
18. Сергей Ожерельев (Поручик) 3536 18.05.10 16:50 Сейчас в теме
Спасибо за ссылку, хотя цветовая гамма там аж глаза режет.
Это всё хорошо (библиотека), но вот формализованное или авторское описание алгоритма Оливера покоя не даёт :)))) Сырцы php смотрел, но в сях не особо силён.
19. Сергей Ожерельев (Поручик) 3536 18.05.10 16:54 Сейчас в теме
(17)
Кстати, твоё творчество здесь разместил.
Оставьте свое сообщение