В практике программирования на платформе 1С иногда возникают задачи, требующие не только знания объектов метаданных, но и навыков построения эффективных алгоритмов для перебора и анализа чисел. Рассмотрим классическую математическую задачу: найти все шестизначные числа, которые не делятся на 10 (не заканчиваются на ноль), не являются палиндромами, но при этом делятся на число, записанное теми же цифрами в обратном порядке.
Проанализируем ситуацию: нам нужно найти такие числа abcdef, для которых abcdef = k * fedcba, где k — целое число больше единицы. Разберем несколько способов реализации этой задачи: от простого перебора до использования языка запросов и математической оптимизации.
Самый очевидный способ для разработчика 1С — это запустить цикл по всем шестизначным числам и проверять условия для каждого из них. Посмотрим на пример реализации, где для получения «перевертыша» используется вспомогательная функция разбора числа по разрядам.
Функция ПолучитьРазвороченноеЧисло(Знач ЧислоИсточник)
Результат = 0;
Пока ЧислоИсточник <> 0 Цикл
Остаток = ЧислоИсточник % 10;
Результат = Результат * 10 + Остаток;
ЧислоИсточник = (ЧислоИсточник - Остаток) / 10;
КонецЦикла;
Возврат Результат;
КонецФункции
Процедура НайтиЧислаБрутфорсом()
Для ТекущееЧисло = 100001 По 999999 Цикл
// Число не должно делиться на 10 по условию
Если ТекущееЧисло % 10 = 0 Тогда
Продолжить;
КонецЕсли;
ОбратноеЧисло = ПолучитьРазвороченноеЧисло(ТекущееЧисло);
// Проверяем, что число делится на свой перевертыш и не равно ему
Если ТекущееЧисло <> ОбратноеЧисло И ОбратноеЧисло <> 0 Тогда
Если ТекущееЧисло % ОбратноеЧисло = 0 Тогда
Сообщить("Найдено число: " + ТекущееЧисло + " / " + ОбратноеЧисло + " = " + (ТекущееЧисло / ОбратноеЧисло));
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот метод прост в написании, но может работать медленно, если диапазон чисел увеличится. Выясним, как можно ускорить процесс получения обратного числа.
Рассмотрим необычный и эффективный способ разворота строки в 1С, предложенный опытными разработчиками. Вместо математического цикла можно использовать возможности платформы по работе с буфером двоичных данных. Метод Перевернуть() у объекта БуферДвоичныхДанных работает крайне быстро.
Процедура НайтиЧислаЧерезБуфер()
ТекущееЧисло = 100001;
Пока ТекущееЧисло <= 999999 Цикл
Если ТекущееЧисло % 10 <> 0 Тогда
// Превращаем число в строку, затем в буфер двоичных данных
Буфер = ПолучитьБуферДвоичныхДанныхИзСтроки(XMLСтрока(ТекущееЧисло));
// Инвертируем порядок байтов (символов) в буфере
ОбратноеЧисло = Число(ПолучитьСтрокуИзБуфераДвоичныхДанных(Буфер.Перевернуть()));
Если ТекущееЧисло <> ОбратноеЧисло И ТекущееЧисло % ОбратноеЧисло = 0 Тогда
Сообщить("" + ТекущееЧисло + " / " + ОбратноеЧисло + " = " + ТекущееЧисло / ОбратноеЧисло);
КонецЕсли;
КонецЕсли;
ТекущееЧисло = ТекущееЧисло + 1;
КонецЦикла;
КонецПроцедуры
Важный момент: Использование буфера двоичных данных позволяет сократить время выполнения алгоритма в несколько раз по сравнению со стандартным циклом перебора цифр.
Проанализируем ситуацию, когда нам нужно найти такие числа средствами SQL-движка. В 1С это можно сделать через формирование временной таблицы цифр и их последующее соединение «само на себя» шесть раз. Это позволит нам сконструировать число и его перевертыш прямо в запросе.
ВЫБРАТЬ 0 КАК Разряд ПОМЕСТИТЬ Разряды ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВсеЧисла.ТекущееЧисло КАК ТекущееЧисло,
ВсеЧисла.ОбратноеЧисло КАК ОбратноеЧисло
ИЗ
(ВЫБРАТЬ
R1.Разряд + 10 * (R2.Разряд + 10 * (R3.Разряд + 10 * (R4.Разряд + 10 * (R5.Разряд + 10 * R6.Разряд)))) КАК ТекущееЧисло,
R6.Разряд + 10 * (R5.Разряд + 10 * (R4.Разряд + 10 * (R3.Разряд + 10 * (R2.Разряд + 10 * R1.Разряд)))) КАК ОбратноеЧисло
ИЗ
Разряды КАК R1, Разряды КАК R2, Разряды КАК R3, Разряды КАК R4, Разряды КАК R5, Разряды КАК R6
ГДЕ
R6.Разряд <> 0 И R1.Разряд <> 0) КАК ВсеЧисла
ГДЕ
ВсеЧисла.ТекущееЧисло <> ВсеЧисла.ОбратноеЧисло
И ВсеЧисла.ТекущееЧисло % ВсеЧисла.ОбратноеЧисло = 0
Этот запрос генерирует все возможные комбинации цифр. Условие R6.Разряд <> 0 гарантирует, что число шестизначное, а R1.Разряд <> 0 — что число не делится на 10. Подобный подход удобен тем, что расчеты переносятся на сторону СУБД.
Выясним причину, по которой поиск можно ускорить еще сильнее. В теории чисел доказано, что для десятичной системы счисления множитель k (результат деления) для таких чисел может принимать только два значения: 4 или 9.
1...9 * 9 = 9...1).2...8 * 4 = 8...2).Все подобные числа строятся вокруг базовых палиндромных кратных: 1089 * 9 = 9801 и 2178 * 4 = 8712. Для шести знаков такими числами являются:
Проанализировав эти свойства, мы понимаем, что вместо перебора 900 000 чисел достаточно проверить только те, что кратны 9, и начинаются на 1 или 2. Это превращает задачу из «тяжелого» перебора в мгновенное вычисление.
В результате мы рассмотрели три различных подхода к решению: классический алгоритм 1С, оптимизированный метод с двоичными данными и мощь языка запросов. Использование математических свойств чисел позволяет сократить время работы программы с секунд до миллисекунд.