Транспортная задача является классической задачей линейного программирования, целью которой является минимизация затрат на перевозку груза от поставщиков к потребителям. Реализация этой задачи в среде 1С:Предприятие 7.7 сопряжена с определенными трудностями, связанными с отсутствием некоторых современных структур данных (например, встроенных многомерных массивов) и особенностями производительности встроенного языка. В этой статье мы подробно разберем, как обойти эти ограничения и эффективно реализовать алгоритм решения.
Прежде всего, нам необходимо определиться с математическим аппаратом. Для решения транспортной задачи обычно используется метод потенциалов. Весь процесс мы можем разделить на два ключевых этапа:
Проанализируем специфику платформы 7.7. Поскольку встроенный интерпретатор работает медленнее, чем в версии 8.х, мы крайне рекомендуем использовать метод аппроксимации Фогеля для формирования начального плана. Он требует чуть больше кода, но дает результат, близкий к оптимальному, что существенно сокращает количество итераций основного цикла пересчета.
Одной из главных претензий разработчиков к 1С 7.7 является отсутствие конструкций вида Массив[x][y]. Разберем основные способы решения этой проблемы, которые предложили участники сообщества.
Способ 1: Использование объекта ТаблицаЗначений
Объект ТаблицаЗначений в 7.7 по сути является готовой двумерной матрицей. Мы можем использовать строки как одну размерность, а колонки — как вторую. Рассмотрим пример создания матрицы тарифов:
// Создаем таблицу для хранения матрицы стоимостей (тарифов)
ТЗ_Тарифы = СоздатьОбъект("ТаблицаЗначений");
Для НомерПоставщика = 1 По КолвоПоставщиков Цикл
ТЗ_Тарифы.НоваяКолонка("П" + НомерПоставщика, "Число");
КонецЦикла;
// Заполнение данных
Для НомерПотребителя = 1 По КолвоПотребителей Цикл
ТЗ_Тарифы.НоваяСтрока();
Для НомерПоставщика = 1 По КолвоПоставщиков Цикл
ТЗ_Тарифы.УстановитьЗначение(НомерПотребителя, НомерПоставщика, Тариф);
КонецЦикла;
КонецЦикла;
Однако помните: если количество поставщиков очень велико (сотни), работа с большим количеством колонок в ТаблицаЗначений может замедляться. В этом случае лучше располагать данные так, чтобы таблица росла «вниз» (больше строк, меньше колонок).
Способ 2: Развертывание в одномерный массив
Проанализируем классический математический прием: любой N-мерный массив можно представить в виде одномерного. Для доступа к элементу матрицы (i, j) используется формула: Индекс = (i - 1) * КоличествоКолонок + j.
// Пример работы с одномерным массивом как с матрицей
РазмерМатрицы = КолвоСтрок * КолвоКолонок;
СписокМатрица = СоздатьОбъект("СписокЗначений"); // Используем СписокЗначений как массив
// Функция записи в "двумерный" массив
Процедура УстановитьЗначениеМатрицы(Список, Строка, Колонка, Значение, ВсегоКолонок)
Индекс = (Строка - 1) * ВсегоКолонок + Колонка;
Список.УстановитьЗначение(Индекс, Значение);
КонецПроцедуры
// Функция чтения
Функция ПолучитьЗначениеМатрицы(Список, Строка, Колонка, ВсегоКолонок)
Индекс = (Строка - 1) * ВсегоКолонок + Колонка;
Возврат Список.ПолучитьЗначение(Индекс);
КонецФункции
Если размерность транспортной задачи велика, встроенный язык 1С 7.7 может не справиться с расчетами за приемлемое время. В этом случае мы рекомендуем рассмотреть два мощных инструмента: OLE Excel и MSScriptControl.
Рассмотрим по шагам, как можно делегировать расчеты программе Microsoft Excel, которая отлично оптимизирована под задачи линейного программирования.
Excel.Application.Solver (Поиск решения).Это самый надежный способ для решения сложных систем уравнений, так как он минимизирует риск ошибок округления, критичных для симплекс-метода.
Выясним, как использовать возможности других языков внутри 1С 7.7. Объект MSScriptControl.ScriptControl позволяет исполнять код на JScript или VBScript. Эти языки поддерживают полноценные массивы и работают быстрее с рекурсивными алгоритмами.
// Пример инициализации скрипта
Скрипт = СоздатьОбъект("MSScriptControl.ScriptControl");
Скрипт.Language = "JScript";
// Передаем код алгоритма метода потенциалов строкой
Скрипт.AddCode("
function SolveTransport(tariffs, supply, demand) {
// Здесь описывается быстрый JS-код решения задачи
return result_matrix;
}
");
// Вызываем расчет
Результат = Скрипт.Run("SolveTransport", МатрицаТарифов, Запасы, Потребности);
При написании кода на языке 1С 7.7 обязательно обратите внимание на следующие моменты:
1. Точность вычислений (Эпсилон)
В задачах линейного программирования часто приходится сравнивать числа с плавающей точкой. В 1С 7.7 при проверке условий оптимальности (равенство потенциалов нулю) всегда используйте небольшую дельту (точность), чтобы избежать зацикливания алгоритма из-за микроскопических погрешностей накопления:
Эпсилон = 0.000001;
Если МаксРазность > Эпсилон Тогда
// План еще не оптимален, продолжаем итерации
КонецЕсли;
2. Проблема вырожденности
Транспортная задача часто бывает вырожденной (количество занятых ячеек меньше, чем M + N - 1). В коде 1С необходимо предусмотреть добавление «нулевой перевозки» (бесконечно малой величины) в соответствующие ячейки, чтобы алгоритм метода потенциалов мог корректно обойти все узлы матрицы.
3. Визуализация результата
Для вывода итогового плана пользователю удобнее всего использовать Таблицу (печатную форму). В ней можно наглядно отобразить и тарифы, и распределенные объемы, выделив оптимальный путь цветом или жирным шрифтом.
Подводя итог, можно сказать, что хотя 1С 7.7 не создавалась для тяжелой математики, сочетание правильного алгоритмического подхода (метод Фогеля + метод потенциалов) и технических хитростей (эмуляция массивов через СписокЗначений или внешние компоненты) позволяет успешно решать логистические задачи оптимизации.