Сортировка массива строк таблицы значений в 1С 8.3: практические методы и оптимизация

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Приветствуем вас! При разработке сложных механизмов на платформе 1С:Предприятие 8.3 мы часто сталкиваемся с необходимостью обработки коллекций данных. Одной из неочевидных, но частых задач является сортировка массива, состоящего из объектов типа СтрокаТаблицыЗначений. Стандартный метод таблицы значений Сортировать() отлично справляется со всей таблицей, но как быть, если у нас на руках лишь массив, содержащий выборку строк, и нам критически важно сохранить их связь с исходной таблицей для последующего редактирования?

Попытка написать собственный простейший алгоритм сортировки (например, пузырьковый метод) на встроенном языке 1С приводит к катастрофическому падению производительности. На объеме всего в 3000 элементов алгоритм "пузырька" начинает выполняться несколько минут или намертво вешает сеанс пользователя. Давайте детально разберем, почему так происходит, и рассмотрим наиболее эффективные, проверенные на практике способы решения этой проблемы.

Почему классический «пузырек» не работает во встроенном языке 1С?

Выясним причину низкой скорости работы пользовательских алгоритмов сортировки. Встроенный язык 1С является интерпретируемым. Это означает, что каждая строка кода, каждое сравнение элементов и перестановка переменных в цикле обрабатываются виртуальной машиной 1С последовательно с огромными накладными расходами. Алгоритм сортировки пузырьком имеет вычислительную сложность O(N^2). Для массива из 3000 элементов это означает выполнение до 9 миллионов операций сравнения и обмена. В условиях интерпретатора такой объем вычислений приводит к перегрузке процессора.

Все штатные методы коллекций (такие как сортировка таблиц значений или списков значений) реализованы разработчиками платформы на компилируемом языке C++ на уровне ядра. Они работают в сотни и тысячи раз быстрее любого кода, написанного вручную на встроенном языке 1С. Поэтому наша главная задача — переложить вычислительную работу по сортировке на встроенные механизмы платформы.

Решение 1. Вспомогательная таблица значений со связью строк (Рекомендуемый метод)

Рассмотрим самый элегантный и производительный способ решения задачи. Суть метода заключается в создании временной таблицы значений, в которую мы копируем только те колонки, по которым будет выполняться сортировка, а также добавляем специальную служебную колонку для хранения прямой ссылки на исходную строку таблицы значений.

Разберем этот алгоритм по шагам:

  1. Получаем исходную таблицу значений, к которой принадлежат строки из массива.
  2. Создаем пустую копию структуры колонок исходной таблицы значений (копируем только те колонки, по которым планируем сортировать).
  3. Добавляем во вспомогательную таблицу колонку с типом данных для хранения ссылки на исходную строку.
  4. Заполняем вспомогательную таблицу данными из массива строк.
  5. Выполняем быструю сортировку вспомогательной таблицы штатным методом Сортировать().
  6. Выгружаем отсортированные ссылки обратно в исходный массив.

Посмотрим на практическую реализацию этого метода в коде:


// Предположим, у нас есть "ИсходныйМассивСтрок", содержащий строки из "ТаблицаИсточника"
// Нам нужно отсортировать этот массив по колонке "Сумма" по убыванию

Если ИсходныйМассивСтрок.Количество() > 0 Тогда

    // Шаг 1. Получаем доступ к исходной таблице через первую строку массива
    ИсходнаяТЗ = ИсходныйМассивСтрок[0].Владелец();
    
    // Шаг 2. Создаем пустую копию колонок, нужных для сортировки
    ВспомогательнаяТЗ = ИсходнаяТЗ.СкопироватьКолонки("Сумма");
    
    // Шаг 3. Добавляем колонку для хранения ссылки на оригинальную строку
    ВспомогательнаяТЗ.Колонки.Добавить("СсылкаНаСтроку");
    
    // Шаг 4. Заполняем вспомогательную ТЗ данными
    Для Каждого СтрокаМассива Из ИсходныйМассивСтрок Цикл
        НоваяСтрока = ВспомогательнаяТЗ.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаМассива);
        НоваяСтрока.СсылкаНаСтроку = СтрокаМассива;
    КонецЦикла;
    
    // Шаг 5. Сортируем вспомогательную ТЗ штатными средствами платформы
    ВспомогательнаяТЗ.Сортировать("Сумма Убыв");
    
    // Шаг 6. Перезаписываем исходный массив отсортированными элементами
    ИсходныйМассивСтрок = ВспомогательнаяТЗ.ВыгрузитьКолонку("СсылкаНаСтроку");

КонецЕсли;

Этот метод работает чрезвычайно быстро. На объеме в 3000 элементов процедура выполняется за доли секунды, так как основная нагрузка по упорядочиванию данных ложится на оптимизированный С++ код платформы.

Важная деталь: избегайте ловушки производительности с методом Индекс()

Проанализируем частую ошибку начинающих разработчиков. Иногда при связывании строк пытаются использовать метод таблицы значений Индекс(), чтобы запомнить числовые индексы строк и сортировать по ним. Метод Индекс() осуществляет линейный перебор строк с самого начала таблицы, что имеет сложность O(N). Если вызывать этот метод в цикле для каждого из 3000 элементов, общая сложность операции снова превратится в O(N^2), и весь выигрыш от быстрой сортировки сойдет на нет. Передача прямой ссылки на объект строки, как показано в примере выше, полностью устраняет эту проблему, так как обращение происходит напрямую за фиксированное время O(1).

Оптимизация сортировки ссылочных типов (СравнениеЗначений)

Проанализируем еще один важный технический нюанс. Если колонка, по которой вы сортируете данные во вспомогательной таблице значений, содержит ссылки на справочники или документы (например, колонка "Контрагент" или "Номенклатура"), стандартный метод Сортировать() начнет сравнивать их по строковому представлению (наименованию). Это заставит платформу выполнять неявные тяжелые запросы к базе данных для получения текстового представления каждого объекта. На больших массивах это вызовет серьезные задержки.

Чтобы избежать этой ловушки, необходимо явно передавать объект сравнения значений. В таком случае сортировка будет выполняться по внутренним уникальным идентификаторам (UUID) ссылок без обращения к СУБД. Посмотрим, как это выглядит в коде:


ОбъектСравнения = Новый СравнениеЗначений;
ВспомогательнаяТЗ.Сортировать("Контрагент Возр", ОбъектСравнения);

Решение 2. Реализация алгоритма быстрой сортировки (QuickSort) на встроенном языке

Если по каким-то причинам вы не хотите создавать вспомогательные таблицы значений (например, в модулях, где наложены жесткие ограничения на использование таблиц, или при работе в облегченных контекстах), можно реализовать классический алгоритм быстрой сортировки Хоара (QuickSort). Сложность этого алгоритма составляет O(N log N), что позволяет ему обрабатывать 3000 элементов за приемлемое время (доли секунды).

Рассмотрим пример рекурсивного алгоритма быстрой сортировки для массива строк ТЗ по определенному свойству:


Процедура БыстраяСортировкаМассиваСтрок(МассивСтрок, ЛевГраница, ПравГраница, ИмяПоляСортировки) Экспорт

    Если ЛевГраница >= ПравГраница Тогда
        Возврат;
    КонецЕсли;

    // Выбираем опорный элемент
    ОпорныйИндекс = ЛевГраница + Цел((ПравГраница - ЛевГраница) / 2);
    ОпорноеЗначение = МассивСтрок[ОпорныйИндекс][ИмяПоляСортировки];

    ИндексЛевый = ЛевГраница;
    ИндексПравый = ПравГраница;

    Пока ИндексЛевый <= ИндексПравый Цикл
        
        Пока МассивСтрок[ИндексЛевый][ИмяПоляСортировки] < ОпорноеЗначение Цикл
            ИндексЛевый = ИндексЛевый + 1;
        КонецЦикла;
        
        Пока МассивСтрок[ИндексПравый][ИмяПоляСортировки] > ОпорноеЗначение Цикл
            ИндексПравый = ИндексПравый - 1;
        КонецЦикла;

        Если ИндексЛевый <= ИндексПравый Тогда
            // Обмениваем элементы местами
            ВременныйЭлемент = МассивСтрок[ИндексЛевый];
            МассивСтрок[ИндексЛевый] = МассивСтрок[ИндексПравый];
            МассивСтрок[ИндексПравый] = ВременныйЭлемент;
            
            ИндексЛевый = ИндексЛевый + 1;
            ИндексПравый = ИндексПравый - 1;
        КонецЕсли;
        
    КонецЦикла;

    // Рекурсивный вызов для левой и правой частей
    Если ЛевГраница < ИндексПравый Тогда
        БыстраяСортировкаМассиваСтрок(МассивСтрок, ЛевГраница, ИндексПравый, ИмяПоляСортировки);
    КонецЕсли;
    Если ИндексЛевый < ПравГраница Тогда
        БыстраяСортировкаМассиваСтрок(МассивСтрок, ИндексЛевый, ПравГраница, ИмяПоляСортировки);
    КонецЕсли;

КонецПроцедуры

Для запуска такой сортировки достаточно вызвать процедуру, передав в нее массив строк, границы и имя реквизита:


БыстраяСортировкаМассиваСтрок(МойМассивСтрок, 0, МойМассивСтрок.Количество() - 1, "Сумма");

Решение 3. Использование встроенного механизма запросов (SQL-way)

Если объем данных действительно велик, а структура колонок сложна, одним из лучших решений будет использование механизма запросов 1С. База данных (или внутренний SQL-движок в файловом варианте) выполняет сортировку на уровне СУБД с максимальной оптимизацией по индексам. Для этого мы можем передать временную таблицу во временную таблицу запроса, выполнить упорядочивание с помощью конструкции УПОРЯДОЧИТЬ ПО и получить готовый результат.

Давайте разберем, как реализовать этот подход:


// Создаем структуру для передачи в запрос
ВременнаяТЗ = Новый ТаблицаЗначений;
ВременнаяТЗ.Колонки.Добавить("ИндексМассива", Новый ОписаниеТипов("Число"));
ВременнаяТЗ.Колонки.Добавить("ЗначениеСортировки");

Для Индекс = 0 По ИсходныйМассивСтрок.Количество() - 1 Цикл
    НоваяСтрока = ВременнаяТЗ.Добавить();
    НоваяСтрока.ИндексМассива = Индекс;
    НоваяСтрока.ЗначениеСортировки = ИсходныйМассивСтрок[Индекс].Сумма;
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВТ_Данные.ИндексМассива КАК ИндексМассива
    |ПОМЕСТИТЬ ВТ_Сортировка
    |ИЗ
    |	&ВременнаяТЗ КАК ВТ_Данные
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_Сортировка.ИндексМассива КАК ИндексМассива
    |ИЗ
    |	ВТ_Сортировка КАК ВТ_Сортировка
    |
    |УПОРЯДОЧИТЬ ПО
    |	ВТ_Сортировка.ЗначениеСортировки УБЫВ";

Запрос.УстановитьПараметр("ВременнаяТЗ", ВременнаяТЗ);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();

// Перестраиваем исходный массив на основе полученных индексов
НовыйМассив = Новый Массив;
Для Каждого СтрокаИндекса Из РезультатЗапроса Цикл
    НовыйМассив.Добавить(ИсходныйМассивСтрок[СтрокаИндекса.ИндексМассива]);
КонецЦикла;

ИсходныйМассивСтрок = НовыйМассив;

Подведение итогов

Мы проанализировали различные методы сортировки массивов строк в системе 1С. Подведем краткие итоги для правильного выбора инструмента:

Используйте эти подходы в своей работе, забудьте про медленные ручные сортировки, и ваши интерфейсы будут работать мгновенно!

← На главную