При разработке интерфейсов с динамическим числом колонок нередко возникает задача: определить, заполнил ли пользователь хотя бы одну значащую ячейку в строке. Когда колонок много (несколько десятков), разработчики стремятся избежать «тяжелых» вложенных циклов вида Строка — Колонка. Рассмотрим подробнее, какие инструменты платформы 1С позволяют решить эту задачу без явного перебора ячеек, где кроются неочевидные подводные камни и как оптимизировать классический алгоритмический обход, если обойтись без него не удается.
Если задача заключается в том, чтобы найти полностью пустые строки (например, содержащие нули во всех анализируемых колонках), наиболее быстрым платформенным решением является метод НайтиСтроки() объекта ТаблицаЗначений. Внутренний механизм платформы написан на C++ и выполняет фильтрацию значительно быстрее циклов на встроенном языке 1С.
Разберем по шагам, как настроить такой отбор:
НайтиСтроки() к готовой таблице.Посмотрим на пример программной реализации:
// Формирование структуры отбора один раз (например, при создании колонок)
ОтборНезаполненных = Новый Структура;
Для Индекс = 4 По ТЗ.Колонки.Количество() - 1 Цикл
ОтборНезаполненных.Вставить(ТЗ.Колонки[Индекс].Имя, 0);
КонецЦикла;
// Быстрый поиск строк, где во всех указанных колонках остались нули
ПустыеСтроки = ТЗ.НайтиСтроки(ОтборНезаполненных);
Ограничение метода: данный подход работает только на строгое равенство одному конкретному значению (например, 0). Если «незаполненной» ячейкой может считаться как 0, так и -1 или Неопределено, стандартный метод НайтиСтроки() применить уже не получится, так как он не поддерживает составные логические условия.
Чтобы не обходить таблицу после завершения ввода, можно фиксировать факт заполнения ячеек в момент интерактивного редактирования пользователем. Для этого в таблицу добавляется служебная булева колонка Изменена или Заполнена.
Однако здесь программисты часто сталкиваются с ошибкой позиционирования. Проанализируем типичную ситуацию: разработчик берет свойство Элемент.ТекущаяСтрока и пытается обратиться по нему к коллекции через квадратные скобки:
// ОШИБОЧНЫЙ КОД:
НомерСтроки = Элемент.ТекущаяСтрока;
ЭтаФорма.РаспределениеСуммПоСчетам[НомерСтроки].Изменена = Истина;
Выясним причину ошибки: в управляемых формах Элемент.ТекущаяСтрока возвращает уникальный числовой идентификатор строки, а не ее порядковый индекс в коллекции. При добавлении, удалении или сортировке строк идентификаторы не соответствуют индексам массива. Попытка подставить идентификатор в квадратные скобки приводит либо к обращению к совершенно другой строке, либо к аварийному завершению из-за выхода за границы массива.
Правильный способ работы с текущей строкой на клиенте без серверных вызовов выглядит следующим образом:
&НаКлиенте
Процедура ТаблицаЧиселПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
Если ОтменаРедактирования Тогда
Возврат;
КонецЕсли;
// Прямой доступ к текущим данным редактируемой строки
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
ТекущиеДанные.Заполнена = Истина;
КонецЕсли;
КонецПроцедуры
Если требуется получить строку коллекции формы по известному идентификатору, следует использовать метод ДанныеФормыКоллекция.НайтиПоИдентификатору(Идентификатор).
Когда таблица требует сложного анализа (проверка на несколько значений: 0, -1, пустые ссылки, параллельное преобразование данных в структуры), без циклов обойтись невозможно. Однако двойной цикл можно существенно ускорить, избегая типичных узких мест.
Разберем две частые проблемы производительности и логики:
Значение <> 0 ИЛИ Значение <> -1 является грубой ошибкой, так как оно всегда истинно для любого числа (ноль не равен минус единице, а минус единица не равна нулю). Корректным является только условие с оператором «И»: Значение <> 0 И Значение <> -1.СтрНачинаетсяС(Колонка.Имя, ...) и обращение к ячейке по имени Строка[Колонка.Имя] на каждой итерации вызывают тяжелый поиск по хэш-таблицам. Колонки по типам и индексам необходимо классифицировать до входа в цикл обработки строк.Посмотрим на оптимизированный пример обхода широкой таблицы значений:
// 1. Предварительная подготовка списков целевых колонок ДО цикла
МассивКолонокСумм = Новый Массив;
Для Каждого Колонка Из ТаблицаДанных.Колонки Цикл
Если СтрНачинаетсяС(Колонка.Имя, "СуммаПеревода")
Или СтрНачинаетсяС(Колонка.Имя, "СуммаИнкассации") Тогда
// Сохраняем имя или индекс колонки
МассивКолонокСумм.Добавить(Колонка.Имя);
КонецЕсли;
КонецЦикла;
// 2. Основной цикл обработки строк
Для Каждого СтрДанных Из ТаблицаДанных Цикл
СтрокаЗаполнена = Ложь;
// Проверяем только целевые колонки
Для Каждого ИмяКолонки Из МассивКолонокСумм Цикл
ТекущееЗначение = СтрДанных[ИмяКолонки];
// Исправленное условие: значение не равно 0 И не равно -1
Если ТекущееЗначение <> 0 И ТекущееЗначение <> -1 Тогда
СтрокаЗаполнена = Истина;
Прервать; // Выходим из цикла по колонкам сразу при первом совпадении
КонецЕсли;
КонецЦикла;
Если СтрокаЗаполнена Тогда
// Выполняем полезную обработку заполненной строки
ОбработатьЗаполненнуюСтроку(СтрДанных);
КонецЕсли;
КонецЦикла;
Выбор оптимального механизма зависит от структуры бизнес-логики:
НайтиСтроки() по предварительно скомпонованной структуре отбора.ТекущиеДанные (помня, что ТекущаяСтрока возвращает идентификатор, а не номер строки).Прервать при первом обнаружении значащего числа.