gifts2017

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

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

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

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

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

[REPLACE WITH]

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

Кстати, для имен, не фамилий, алгоритмы нормально работают?
16. Сергей Ожерельев (Поручик) 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. Сергей Ожерельев (Поручик) 18.05.10 16:50
Спасибо за ссылку, хотя цветовая гамма там аж глаза режет.
Это всё хорошо (библиотека), но вот формализованное или авторское описание алгоритма Оливера покоя не даёт :)))) Сырцы php смотрел, но в сях не особо силён.
19. Сергей Ожерельев (Поручик) 18.05.10 16:54
(17)
Кстати, твоё творчество здесь разместил.