При обработке данных в 1С 7.7 разработчики часто сталкиваются с необходимостью удалить дублирующиеся строки в табличной части (ТЧ) документа или в объекте ТаблицаЗначений по определенному реквизиту. Использование классического двойного цикла для попарного сравнения всех строк неэффективно и приводит к серьезным задержкам на больших объемах данных. Разберем наиболее быстрые и изящные способы решения этой задачи.
Если требуется удалить дубликаты непосредственно из табличной части открытого документа или формы, оптимальным подходом является перебор строк в обратном порядке (от последней к первой). В процессе обхода мы фиксируем уже встретившиеся значения во вспомогательном объекте СписокЗначений.
Почему важно выполнять цикл именно с конца? При удалении текущей строки методом УдалитьСтроку() все последующие строки сдвигаются вверх, меняя свои номера. Обход с конца гарантирует, что изменение состава строк не собьет нумерацию еще не обработанных позиций.
Посмотрим на программный код реализации этого метода:
КолСтрок = КоличествоСтрок();
СЗ = СоздатьОбъект("СписокЗначений");
Пока КолСтрок > 0 Цикл
ПолучитьСтрокуПоНомеру(КолСтрок);
Если СЗ.НайтиЗначение(Реквизит) = 0 Тогда
СЗ.ДобавитьЗначение(Реквизит);
Иначе
УдалитьСтроку();
КонецЕсли;
КолСтрок = КолСтрок - 1;
КонецЦикла;
Важный момент: метод НайтиЗначение() объекта СписокЗначений выполняет линейный перебор элементов. При обработке очень больших списков (от нескольких тысяч строк) скорость поиска может снижаться.
Когда требуется быстро определить сам факт наличия дублей и оставить только уникальные комбинации, удобно использовать методы объекта ТаблицаЗначений. Мы выгружаем табличную часть, добавляем счетчик и выполняем группировку.
Разберем по шагам последовательность действий:
ВыгрузитьТабличнуюЧасть().НоваяКолонка() и заполняем её единицами.Свернуть(), передав в качестве первого параметра группировочные колонки, а в качестве второго — колонку-счетчик.Рассмотрим пример кода:
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ВыгрузитьТабличнуюЧасть(ТЗ);
ТЗ.НоваяКолонка("СчетчикДублей", "Число", 5, 0);
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
ТЗ.СчетчикДублей = 1;
КонецЦикла;
// Сворачиваем по проверяемому реквизиту с суммированием счетчика
ТЗ.Свернуть("ИскомыйРеквизит", "СчетчикДублей");
// ТЗ теперь содержит только уникальные значения
// Строки со значением СчетчикДублей > 1 исходно имели дубликаты
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Если ТЗ.СчетчикДублей > 1 Тогда
Сообщить("Значение " + СокрЛП(ТЗ.ИскомыйРеквизит) + " дублируется " + ТЗ.СчетчикДублей + " раз(а).");
КонецЕсли;
КонецЦикла;
Еще один классический алгоритм без использования вложенных циклов — предварительная сортировка таблицы значений по ключевому реквизиту. После упорядочивания все одинаковые элементы гарантированно оказываются рядом, что позволяет выявить повторы за один проход, сравнивая текущее значение с предыдущим.
Проанализируем пример кода:
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ВыгрузитьТабличнуюЧасть(ТЗ);
// Сортируем таблицу по ключевому полю
ТЗ.Сортировать("ИскомыйРеквизит+");
ПредыдущееЗначение = "";
НомерСтр = ТЗ.КоличествоСтрок();
Пока НомерСтр > 0 Цикл
ТЗ.ПолучитьСтрокуПоНомеру(НомерСтр);
Если НомерСтр < ТЗ.КоличествоСтрок() Тогда
Если ТЗ.ИскомыйРеквизит = ПредыдущееЗначение Тогда
ТЗ.УдалитьСтроку(НомерСтр);
КонецЕсли;
КонецЕсли;
ПредыдущееЗначение = ТЗ.ИскомыйРеквизит;
НомерСтр = НомерСтр - 1;
КонецЦикла;
Для мгновенной обработки десятков тысяч строк в 1С 7.7 платформенных возможностей СписокЗначений может быть недостаточно из-за сложности поиска. В таких случаях рекомендуется использовать внешний COM-объект Scripting.Dictionary (хеш-таблица Windows), где метод Exists() работает практически мгновенно.
Посмотрим на пример реализации:
ХешТаблица = СоздатьОбъект("Scripting.Dictionary");
КолСтрок = КоличествоСтрок();
Пока КолСтрок > 0 Цикл
ПолучитьСтрокуПоНомеру(КолСтрок);
Ключ = СокрЛП(ВРег(Строка(Реквизит)));
Если ХешТаблица.Exists(Ключ) = 0 Тогда
ХешТаблица.Add(Ключ, 1);
Иначе
УдалитьСтроку();
КонецЕсли;
КолСтрок = КолСтрок - 1;
КонецЦикла;
При программном поиске дубликатов строковых реквизитов (например, наименований контрагентов, паспортных данных, телефонов) важно учитывать особенности строкового сравнения:
ВРег() и отсекать концевые пробелы функцией СокрЛП().НайтиПоНаименованию(Имя, 0, 0) вторым параметром принимает режим соответствия: 0 — точное совпадение, 1 — поиск по начальной части. По умолчанию в 1С 7.7 включен поиск по началу строки, что может привести к ложному определению дублей.