При разработке на платформе 1С:Предприятие часто возникает задача консолидации данных внутри ТаблицыЗначений. Например, когда необходимо объединить строки, показатели которых не достигают определенного порога, с последующими или предыдущими строками. В этой статье мы подробно разберем, как правильно реализовать этот механизм, избежав типичных ошибок вроде «Индекс находится за границами массива», и рассмотрим несколько подходов к решению.
Представим ситуацию: у нас есть таблица с колонкой Число. Нам нужно обойти таблицу и, если значение в текущей строке меньше 5, прибавить это значение к следующей строке, а текущую строку удалить. Основная сложность здесь заключается в том, что при удалении строки в процессе обхода индексы всех последующих строк смещаются, что приводит к некорректной работе цикла.
Проанализируем первый вариант решения, предложенный в обсуждении, который использует вспомогательный массив для удаления строк. Это позволяет избежать проблем со смещением индексов непосредственно в момент проверки условий.
Рассмотрим по шагам реализацию через создание массива ссылок на строки, которые подлежат удалению. Этот способ удобен тем, что мы сначала выполняем все математические операции, а затем очищаем таблицу.
// Создаем массив, куда будем складывать строки для удаления
СтрокиКУдалению = Новый Массив;
// Важно: ограничиваем цикл ТЗ.Количество() - 2,
// чтобы при обращении к индексу Н + 1 не выйти за пределы таблицы
Для Н = 0 По ТЗ.Количество() - 2 Цикл
СтрТекущая = ТЗ[Н];
СтрСледующая = ТЗ[Н + 1];
Если СтрТекущая.Число < 5 Тогда
// Переносим значение в следующую строку
СтрСледующая.Число = СтрСледующая.Число + СтрТекущая.Число;
// Помечаем текущую строку на удаление
СтрокиКУдалению.Добавить(СтрТекущая);
КонецЕсли;
КонецЦикла;
// Вторым проходом удаляем накопленные строки
Для каждого Стр Из СтрокиКУдалению Цикл
ТЗ.Удалить(Стр);
КонецЦикла;
Важный нюанс: Обратите внимание на условие завершения цикла ТЗ.Количество() - 2. Если ваша таблица содержит 10 строк (индексы от 0 до 9), то на последней итерации Н будет равно 8. При этом выражение ТЗ[Н+1] обратится к 9-й строке (последней). Если бы мы использовали ТЗ.Количество() - 1, то при Н = 9 попытка обратиться к ТЗ[10] вызвала бы ошибку.
Профессиональные разработчики 1С часто используют обход таблицы «с конца». Рассмотрим ситуацию, когда нам нужно удалять строки сразу. При движении от последней строки к первой удаление текущего элемента никак не влияет на индексы тех строк, которые нам еще только предстоит обработать (так как они находятся «выше»).
Однако в нашей задаче есть особенность: значение нужно передавать следующей строке. Поэтому при обратном обходе мы будем передавать значение «вперед» по индексу, который уже был проверен. Разберем этот пример:
ВсегоСтрок = ТЗ.Количество();
Для Индекс = -(ВсегоСтрок - 1) По 0 Цикл
Н = -Индекс; // Получаем индекс от последнего к первому
// Проверяем, что это не последняя строка, так как ей некуда передавать значение
Если Н < ВсегоСтрок - 1 Тогда
СтрТекущая = ТЗ[Н];
Если СтрТекущая.Число < 5 Тогда
ТЗ[Н + 1].Число = ТЗ[Н + 1].Число + СтрТекущая.Число;
ТЗ.Удалить(СтрТекущая);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Разберем ситуацию, описанную в сообщении №3: что делать, если последняя строка таблицы оказалась меньше порогового значения? В стандартном алгоритме ей некуда передавать остаток. В таком случае логично передать значение предыдущей строке, чтобы общая сумма по колонке в рамках всей таблицы осталась неизменной.
Выясним причину, почему это важно: если мы просто удалим последнюю строку, данные будут потеряны. Посмотрим на пример кода с обработкой этого исключения:
Колво = ТЗ.Количество();
Если Колво > 1 Тогда
СтрокиДляУдаления = Новый Массив;
Для Н = 0 По Колво - 1 Цикл
Стр = ТЗ[Н];
Если Стр.Число < 5 Тогда
Если Н < ТЗ.Количество() - 1 Тогда
// Передаем вперед
ТЗ[Н + 1].Число = ТЗ[Н + 1].Число + Стр.Число;
Иначе
// Это последняя строка, передаем назад
// Находим последнюю оставшуюся строку, которая не помечена на удаление
// Или просто прибавляем к предыдущей, если алгоритм простой
ТЗ[Н - 1].Число = ТЗ[Н - 1].Число + Стр.Число;
КонецЕсли;
СтрокиДляУдаления.Добавить(Стр);
КонецЕсли;
КонецЦикла;
Для каждого Стр Из СтрокиДляУдаления Цикл
ТЗ.Удалить(Стр);
КонецЦикла;
КонецЕсли;
Проанализируем альтернативный подход. Если ваша задача — не просто перекинуть остаток, а объединить данные по определенным ключам (например, по Сотруднику или Проекту), 1С предоставляет мощный встроенный метод Свернуть(). Он работает значительно быстрее ручного обхода.
Рассмотрим сценарий: нам нужно схлопнуть все записи, чтобы не было дублей, и при этом не важен порядок строк. Мы можем сначала программно изменить ключевые поля у «мелких» строк, а затем вызвать Свернуть. Но чаще всего этот метод используется просто для агрегации:
ТЗ.Свернуть("Сотрудник, Проект", "Число");
После этой операции в таблице останутся уникальные комбинации сотрудника и проекта, а все числа по ним просуммируются. Если же вам нужно именно пороговое значение, то ручной обход, описанный выше, остается приоритетным.
Если в вашей таблице десятки тысяч строк, метод ТЗ.Удалить() может вызвать замедление работы, так как при каждом удалении система пересчитывает индексы. В таком случае мы рекомендуем следующий алгоритм:
НоваяТЗ = ТЗ.СкопироватьКолонки();НакопленныйОстаток = 0;Число + НакопленныйОстаток < 5, то просто прибавляем значение к НакопленныйОстаток.НоваяТЗ, записываем туда сумму и обнуляем НакопленныйОстаток.НоваяТЗ.Такой подход работает быстрее, так как операции добавления в конец таблицы менее ресурсоемки, чем удаление из середины.
Мы рассмотрели различные способы переноса значений между строками ТаблицыЗначений. Выбор конкретного метода зависит от объема ваших данных и требований к точности (нужно ли обрабатывать последнюю строку отдельно). Помните о следующих правилах:
ТЗ.Количество() перед обращением к индексам.Null или Неопределено.