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