В практике разработки на платформе 1С:Предприятие часто возникает задача поиска данных, которые могут быть введены в разных раскладках. Это актуально для артикулов, VIN-номеров автомобилей, серийных номеров или государственных знаков. Пользователи часто путают визуально идентичные символы, такие как русская "А" и английская "A", или цифру "0" и букву "О". Разберем, как правильно реализовать алгоритм перебора всех возможных комбинаций написания слова и какие нюансы следует учитывать для обеспечения стабильности системы.
Символы, которые выглядят одинаково, но имеют разные коды в таблице Unicode, называются омографами. Для платформы 1С символы "А" (кириллица) и "A" (латиница) — это абсолютно разные объекты. Если в базе данных артикул записан латиницей, а пользователь ищет его, используя русскую раскладку, стандартный поиск не даст результатов. Чтобы решить эту задачу, программисты обычно используют два подхода: либо нормализацию (приведение всех строк к одному стандарту при записи), либо генерацию вариантов (поиск по всем возможным комбинациям написания).
Рассмотрим попытку реализации перебора через рекурсивную функцию. Этот метод кажется интуитивно понятным: мы идем по каждому символу строки и, если он имеет "двойника", вызываем ту же функцию для каждого варианта замены. Проанализируем пример кода:
&НаКлиенте
Процедура Команда1(Команда)
СписокПохожестей = Новый Массив;
СписокПохожестей.Добавить("АаAa");
СписокПохожестей.Добавить("РрPp");
СписокПохожестей.Добавить("ОоOo0");
ВходноеСлово = "АРО";
ПеребратьСимволВСлове(СписокПохожестей, ВходноеСлово, СтрДлина(ВходноеСлово));
КонецПроцедуры
Процедура ПеребратьСимволВСлове(СписокПохожестей, Знач ВходноеСлово, Знач НомерСимвола)
Если НомерСимвола < 1 Тогда
Сообщить(ВходноеСлово);
Возврат;
КонецЕсли;
ПроверяемыйСимвол = Сред(ВходноеСлово, НомерСимвола, 1);
Для Каждого СтрокаПохожести из СписокПохожестей Цикл
Если СтрНайти(СтрокаПохожести, ПроверяемыйСимвол) > 0 Тогда
ЛеваяЧастьСлова = Лев(ВходноеСлово, НомерСимвола - 1);
ПраваяЧастьСлова = Сред(ВходноеСлово, НомерСимвола + 1);
Для НомерВСтрокеПохожести = 1 по СтрДлина(СтрокаПохожести) Цикл
НовоеСлово = ЛеваяЧастьСлова + Сред(СтрокаПохожести, НомерВСтрокеПохожести, 1) + ПраваяЧастьСлова;
ПеребратьСимволВСлове(СписокПохожестей, НовоеСлово, НомерСимвола - 1);
КонецЦикла;
Прервать;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В данном коде используется классическая рекурсия. Однако при работе с длинными строками или большим количеством похожих символов возникает проблема экспоненциального роста количества комбинаций. Например, если в слове 10 заменяемых позиций и у каждой по 2 варианта, мы получим 1024 итерации. Если же вариантов замены больше (как в случае с "О", "о", "O", "o", "0"), количество комбинаций быстро переполнит стек вызовов, что приведет к аварийному завершению работы 1С.
Более надежным и производительным методом является приведение строки к единому стандарту (например, замена всей латиницы на кириллицу) перед сравнением. Это исключает необходимость перебора. Рассмотрим функции, которые позволяют выполнить такую трансформацию:
Функция ЗаменитьЛатыньНаКирилицу(ТекстоваяСтрока)
Лат = "AaBCcEeHKkMmnOoPpTuXxy";
Рус = "АаВСсЕеНКкМтпОоРрТиХху";
Результат = ТекстоваяСтрока;
Для сч = 1 по СтрДлина(Результат) Цикл
тПоз = Найти(Лат, Сред(Результат, сч, 1));
Если тПоз > 0 Тогда
// Заменяем конкретный символ по его позиции в эталонной строке
Результат = СтрЗаменить(Результат, Сред(Лат, тПоз, 1), Сред(Рус, тПоз, 1));
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Функция ЗаменитьКирилицуНаЛатынь(ТекстоваяСтрока)
Лат = "AaBCcEeHKkMmnOoPpTuXxy";
Рус = "АаВСсЕеНКкМтпОоРрТиХху";
Результат = ТекстоваяСтрока;
Для сч = 1 по СтрДлина(Результат) Цикл
тПоз = Найти(Рус, Сред(Результат, сч, 1));
Если тПоз > 0 Тогда
Результат = СтрЗаменить(Результат, Сред(Рус, тПоз, 1), Сред(Лат, тПоз, 1));
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Выясним причину, почему этот способ предпочтительнее. Используя такие функции при записи объекта, вы можете сохранять "нормализованный" артикул в отдельное индексированное поле. При поиске поисковый запрос пользователя также прогоняется через эту функцию. В итоге поиск в базе данных выполняется по одному конкретному значению, что работает мгновенно даже на миллионах записей.
Проанализируем продвинутый способ генерации комбинаций, упомянутый опытными разработчиками — использование пакетных запросов. Этот метод позволяет переложить работу по комбинированию символов на СУБД, что часто быстрее, чем циклы в коде 1С.
Суть метода заключается в следующем:
В результате получается результат запроса, содержащий все возможные варианты написания слова. Этот подход избавляет от рекурсии и жестко ограничивает количество комбинаций на уровне логики запроса.
Для создания действительно качественной системы поиска, проанализируем наиболее полный список символов, которые визуально неразличимы в большинстве шрифтов:
Важно помнить, что функция СтрНайти и оператор ПОДОБНО в запросах могут вести себя по-разному в зависимости от настроек чувствительности к регистру в базе данных (Collation SQL), но они всегда чувствительны к раскладке (Unicode-коду символа).
Подводя итог, проанализируем лучшие практики для решения задачи перебора:
RegExp (через COM-объект VBScript.RegExp), где для каждой позиции можно задать набор альтернатив, например [АA][РP][ОO0].Таким образом, выбор метода зависит от конкретной задачи: для разовой обработки подойдет нормализация функциями замены, а для построения сложных поисковых систем — создание дополнительных поисковых индексов с предварительно обработанными данными.