В практике программирования на платформе 1С:Предприятие часто возникают задачи, связанные с планированием графиков, расчетом периодических событий и накоплением итогов за определенные интервалы времени (например, недели или месяцы). Рассмотрим типичную задачу: необходимо рассчитать потребление определенного продукта (в нашем примере — протеина) на период 21 день, при условии, что прием осуществляется строго каждый третий день, а нормы потребления различаются для будней и выходных. При этом все данные должны быть сохранены в массив, а результаты выведены с группировкой по неделям.
Разберем основные условия, которые нам необходимо реализовать в программном коде:
Массив.Проанализируем ситуацию: для решения нам потребуется организовать цикл по количеству дней и внутри него реализовать проверку условий. Рассмотрим пошагово, как это сделать правильно с точки зрения архитектуры 1С.
В 1С Массив может хранить значения любого типа. Чтобы хранить "всю информацию" об одном дне, лучше всего использовать объект Структура. Рассмотрим подробнее, почему это удобно: структура позволяет обращаться к данным по именам полей (например, День.Количество), что делает код читаемым. Выясним причину, по которой обычный массив массивов менее удобен — в нем легко запутаться в индексах (0, 1, 2...).
Посмотрим на пример инициализации базовых переменных:
КоличествоДней = 21;
ЧастотаПриема = 3;
ПротеинВБудни = 100; // грамм
ПротеинВВыходные = 150; // грамм
МассивДанных = Новый Массив;
ОбщийИтог = 0;
ИтогЗаНеделю = 0;
Разберем, как правильно определить, является ли текущий день днем приема. Самый простой и эффективный способ — использование оператора остатка от деления %. Если номер дня делится на частоту приема без остатка, значит, это нужный нам день. Рассмотрим пример реализации логики внутри цикла Для...По.
Проанализируем расчет типа дня (будни или выходные). Поскольку мы начинаем с понедельника, мы можем использовать простую арифметику: дни с 1 по 5 — будни, 6 и 7 — выходные. Для любого дня номер дня в рамках недели можно вычислить как ((НомерДня - 1) % 7) + 1.
Для НомерДня = 1 По КоличествоДней Цикл
// 1. Определяем, является ли день днем приема
ЭтоДеньПриема = (НомерДня % ЧастотаПриема = 0);
// 2. Определяем день недели (с учетом старта с понедельника)
ДеньНедели = ((НомерДня - 1) % 7) + 1;
ЯвляетсяВыходным = (ДеньНедели = 6 Или ДеньНедели = 7);
// 3. Расчитываем количество
ТекущееКоличество = 0;
Если ЭтоДеньПриема Тогда
ТекущееКоличество = ?(ЯвляетсяВыходным, ПротеинВВыходные, ПротеинВБудни);
КонецЕсли;
// 4. Сохраняем данные в структуру и массив
ДанныеДня = Новый Структура;
ДанныеДня.Вставить("Номер", НомерДня);
ДанныеДня.Вставить("Прием", ЭтоДеньПриема);
ДанныеДня.Вставить("Количество", ТекущееКоличество);
МассивДанных.Добавить(ДанныеДня);
КонецЦикла;
Теперь рассмотрим задачу вывода еженедельных итогов. Как было верно замечено в обсуждении на форуме, нам необходим дополнительный счетчик ИтогЗаНеделю. Мы будем накапливать в нем значения и выводить их в тот момент, когда заканчивается очередная семидневка. Выясним, как это реализовать элегантно внутри того же самого цикла, чтобы не плодить лишние итерации.
Разберем по шагам логику вывода:
ДеньНедели = 7) или последним днем всего периода (НомерДня = КоличествоДней).
НомерНедели = 1;
ИтогЗаНеделю = 0;
ОбщийИтог = 0;
Для Каждого Элемент Из МассивДанных Цикл
ИтогЗаНеделю = ИтогЗаНеделю + Элемент.Количество;
ОбщийИтог = ОбщийИтог + Элемент.Количество;
// Определяем день недели для текущего элемента
ТекущийДеньНедели = ((Элемент.Номер - 1) % 7) + 1;
Если ТекущийДеньНедели = 7 Или Элемент.Номер = КоличествоДней Тогда
Сообщить("Неделя " + НомерНедели + " - " + ИтогЗаНеделю + "гр");
ИтогЗаНеделю = 0; // Обнуляем для новой недели
НомерНедели = НомерНедели + 1;
КонецЕсли;
КонецЦикла;
Сообщить("Всего - " + ОбщийИтог + "гр");
В реальных задачах 1С мы редко оперируем просто абстрактными числами (1, 2, 3...). Чаще всего работа идет с объектами типа Дата. Рассмотрим, как сделать наш алгоритм более универсальным, используя встроенные функции платформы, такие как ДеньНедели() и ДобавитьМесяц(). Это позволит избежать ручного вычисления выходных по номеру дня.
Посмотрим на пример кода, который учитывает реальные календарные даты:
ДатаНачала = НачалоНедели(ТекущаяДата()); // Всегда начинаем с понедельника текущей недели
ВсегоДней = 21;
МассивРезультатов = Новый Массив;
Для Индекс = 0 По ВсегоДней - 1 Цикл
ТекущаяДатаЦикла = ДатаНачала + (Индекс * 86400); // Прибавляем секунды (в сутках 86400 сек)
НомерДняВГрафике = Индекс + 1;
ЭтоДеньПриема = (НомерДняВГрафике % ЧастотаПриема = 0);
ЧислоДняНедели = ДеньНедели(ТекущаяДатаЦикла);
Если ЭтоДеньПриема Тогда
Расход = ?(ЧислоДняНедели > 5, ПротеинВВыходные, ПротеинВБудни);
Иначе
Расход = 0;
КонецЕсли;
// Хранение данных
Запись = Новый Структура("Дата, Количество, Прием", ТекущаяДатаЦикла, Расход, ЭтоДеньПриема);
МассивРезультатов.Добавить(Запись);
КонецЦикла;
Проанализируем ситуацию, упомянутую в сообщении 23 форума: "нельзя внутри цикла менять переменную, являющуюся счетчиком". Это золотое правило разработки. Если вы используете цикл Для Счетчик = 1 По 21, попытка изменить Счетчик внутри тела цикла может привести к непредсказуемому поведению или бесконечному циклу. Если вам нужно гибкое управление итерациями, используйте цикл Пока.
Также стоит предостеречь от использования функции Выполнить(), предложенной в некоторых сообщениях. В 1С это считается плохим тоном (антипаттерном) для простых математических задач. Это снижает производительность и затрудняет отладку кода. Всегда отдавайте предпочтение явным вычислениям и обращению к свойствам структур или колонкам ТаблицыЗначений.
Подводя итог, отметим, что для успешного решения задач на циклы и массивы в 1С важно четко разделять логику расчета (накопление данных в массив) и логику представления (вывод итогов пользователю). Использование Структуры внутри Массива — это стандарт де-факто для хранения наборов разнородных данных в оперативной памяти. Комбинация математического оператора остатка от деления и встроенных функций работы с датами позволяет создавать надежный и легко читаемый код, который легко адаптировать под любые изменения условий задачи.