Разработка и Конфигурация · 12 мин
Как удалить дубли в списке значений 1С
Как удалить дубли в списке значений 1С: свёртка через таблицу значений, соответствие, перебор с проверкой НайтиПоЗначению. Примеры кода, ошибки и проверка результата.
Дубли в списке значений (объект СписокЗначений) — типичная ситуация при сборе данных из нескольких источников: выгрузки документов, результаты запросов, пользовательский множественный выбор. Повторяющиеся элементы искажают отчёты, приводят к двойной обработке одних и тех же ссылок и лишним строкам в табличных частях.
Встроенного метода «УдалитьДубли» у списка значений нет, поэтому задача решается обходными путями: через таблицу значений со свёрткой, через соответствие или через контролируемый перебор с проверкой наличия. Каждый способ имеет свои ограничения по скорости, сохранению порядка элементов и представлений.
Ниже разобраны рабочие варианты кода, критерии выбора между ними, типичные ошибки и способы проверить, что очистка выполнена корректно и ничего лишнего не потеряно.
Как устроен список значений и почему в нём появляются дубли
Список значений — это упорядоченная коллекция, где каждый элемент хранит четыре свойства: Значение, Представление, Пометка и Картинка. Платформа не контролирует уникальность: метод Добавить() вставляет элемент без каких-либо проверок, поэтому дубль появляется молча.
Типичные источники дублей: объединение результатов нескольких запросов через Выгрузить() и последующую загрузку в список, циклическое добавление ссылок из табличных частей, множественный выбор пользователя в диалоге ВыбратьИзСписка при повторном открытии формы, загрузка данных из файлов, где один контрагент встречается в нескольких строках.
Важный нюанс: «дубль» можно определять по-разному. Два элемента с одинаковым значением, но разными представлениями — формально разные элементы. Одинаковые представления при разных значениях — тоже не дубли. Перед очисткой решите, что считать дублем: чаще всего совпадение именно свойства Значение.
Способ 1. Свернуть дубли через таблицу значений
Самый короткий и надёжный приём — выгрузить список в таблицу значений и применить метод Свернуть(). Он группирует строки по указанным колонкам, удаляя дубли автоматически. Подходит, когда порядок элементов не критичен, а представления либо совпадают, либо не нужны.
Пример кода:
Функция УдалитьДублиЧерезТаблицу(Список)
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Значение");
Для Каждого Элемент Из Список Цикл
Таблица.Добавить().Значение = Элемент.Значение;
КонецЦикла;
Таблица.Свернуть("Значение");
Результат = Новый СписокЗначений;
Результат.ЗагрузитьЗначения(Таблица.ВыгрузитьКолонку("Значение"));
Возврат Результат;
КонецФункции
Обратите внимание: при свёртке по колонке Значение представления и пометки теряются. Если они нужны, добавьте колонку Представление и сворачивайте по обеим колонкам — но тогда элементы с одинаковым значением и разными представлениями останутся в списке, что соответствует их формальной «не-дубльности».
Метод хорошо масштабируется: свёртка выполняется встроенными механизмами платформы и на десятках тысяч строк работает заметно быстрее ручного перебора с поиском.
Способ 2. Убрать повторы через соответствие
Если важно сохранить порядок первого появления элементов, удобнее использовать объект Соответствие: он не допускает дублирующихся ключей, и проверка наличия ключа выполняется быстро даже на больших объёмах.
Функция УдалитьДублиЧерезСоответствие(Список)
Уникальные = Новый Соответствие;
Результат = Новый СписокЗначений;
Для Каждого Элемент Из Список Цикл
Если Уникальные[Элемент.Значение] = Неопределено Тогда
Уникальные.Вставить(Элемент.Значение, Истина);
Результат.Добавить(Элемент.Значение, Элемент.Представление);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Здесь сохраняется и порядок, и представление первого встреченного элемента. Последующие дубли просто пропускаются. Это оптимальный вариант, когда список потом показывается пользователю в отсортированном вручную порядке или порядок имеет смысл (например, хронология документов).
Ограничение: ключом соответствия может быть значение любого типа, но сравнение идёт по значению ключа. Для ссылок это работает корректно, а вот для сложных составных типов поведение стоит проверить на тестовых данных.
Сравнение вариантов
Самый быстрый на больших объёмах, код короткий. Теряются порядок, пометки и представления, если их не добавить отдельными колонками.
Сохраняет порядок первого появления и представление. Чуть больше кода, хорошая скорость благодаря быстрому поиску по ключу.
Простейший код, но на каждом шаге ищет по списку линейно — заметно медленнее при тысячах элементов. Подходит для небольших списков.
Лучший вариант, если данные ещё не собраны в список: дубли устраняются на уровне запроса, и промежуточная очистка вообще не нужна.
Способ 3. Перебор с проверкой НайтиПоЗначению
Для небольших списков (десятки, от силы сотни элементов) достаточно прямолинейного перебора: каждый элемент проверяется через НайтиПоЗначению() в новом списке, и добавляется только при отсутствии.
Функция УдалитьДублиПеребором(Список)
Результат = Новый СписокЗначений;
Для Каждого Элемент Из Список Цикл
Если Результат.НайтиПоЗначению(Элемент.Значение) = Неопределено Тогда
Результат.Добавить(Элемент.Значение, Элемент.Представление, Элемент.Пометка);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Метод НайтиПоЗначению выполняет линейный поиск, поэтому общая стоимость растёт квадратично: на списке в несколько тысяч элементов задержка уже ощутима. Для форм с небольшими подборами это некритично, для пакетной обработки данных — выбирайте свёртку или соответствие.
Преимущество подхода — полный контроль: здесь легко сохранить пометки, заменить представление на последнее встреченное вместо первого или залогировать каждый отброшенный дубль для анализа источника проблемы.
Как не допустить дублей до их появления
Дешевле не чистить список, а не допускать повторов на этапе наполнения. Перед каждым Добавить() выполняйте проверку НайтиПоЗначению() — для умеренных объёмов это проще, чем последующая очистка, и список остаётся консистентным в любой момент времени.
Если данные берутся из запроса, устраняйте дубли средствами СКД-языка запросов: ВЫБРАТЬ РАЗЛИЧНЫЕ для уникальных строк или группировка СГРУППИРОВАТЬ ПО по нужному полю. Тогда в список попадут уже очищенные данные, и промежуточная обработка не понадобится вовсе.
При объединении нескольких списков используйте промежуточное соответствие как «регистр уникальности»: добавляйте в итоговый список только те значения, которых ещё нет среди ключей. Это стандартный паттерн для слияния выборок из разных источников.
- 1Определите, что считается дублем: совпадение значения, представления или обоих свойств
- 2Оцените размер списка и важность порядка элементов
- 3Для больших списков без требований к порядку используйте свёртку таблицы значений
- 4Если порядок и представления важны — примените соответствие
- 5Для маленьких списков достаточно перебора с НайтиПоЗначению
- 6Проверьте результат: сравните количество элементов до и после, убедитесь, что уникальные значения не потеряны
Проверка результата и типичные ошибки
После очистки убедитесь, что результат корректен. Минимальная проверка — количество элементов в новом списке должно равняться количеству уникальных значений исходного. Простой способ убедиться: повторно прогнать новый список через ту же функцию очистки — размер не должен измениться.
Дополнительно сверьте, что все ожидаемые значения на месте: пройдитесь по исходному списку и для каждого уникального значения проверьте НайтиПоЗначению() в результате. Потерянное значение — признак ошибки в логике (например, сравнение ссылок разных типов или обрезанные строки).
Отдельная ловушка — модификация списка во время итерации. Если вы пишете Для Каждого Элемент Из Список Цикл ... Список.Удалить(Элемент), коллекция изменяется прямо в цикле, и результат непредсказуем. Либо удаляйте по индексам в обратном направлении (Для Индекс = -Список.Количество() + 1 ... с обратным шагом), либо формируйте новый список — второй вариант надёжнее и читаемее.
Как проверить результат
Размер не меняется — дублей не осталось
Размер уменьшился — очистка сработала не полностью
Каждое уникальное значение находится через НайтиПоЗначению
Часть значений потеряна — ошибка в логике сравнения
Сохранены, если это требовалось
Сброшены — нужно переносить их явно при добавлении
Соответствует ожиданиям (первое вхождение или сортировка)
Произвольный порядок там, где важна последовательность
Особые случаи: строки, ссылки и разные типы
Для строковых значений перед сравнением применяйте нормализацию: СокрЛП() убирает краевые пробелы, а при необходимости — приведение к одному регистру через ВРег() или НРег(). Иначе визуально одинаковые наименования останутся в списке как разные элементы.
Для ссылочных типов сравнение работает корректно «из коробки»: две ссылки на один и тот же объект равны. Но ссылки на разные объекты с одинаковым представлением (два контрагента с одним наименованием) — не дубли, и удалять их по представлению опасно: можно потерять реально разные объекты.
Смешанные списки, где часть элементов — строки, а часть — ссылки, обрабатывайте с осторожностью: свёртка таблицы значений по колонке составного типа работает, но убедитесь, что колонка объявлена без ограничения типа или с нужным составом типов, иначе значения могут быть приведены или отброшены.
FAQ
Есть ли у списка значений встроенный метод удаления дублей?
Нет. У объекта СписокЗначений есть методы поиска и удаления по элементу или индексу, но автоматической дедупликации нет. Задача решается через свёртку таблицы значений, соответствие или перебор с проверкой наличия.
Как удалить дубли, сохранив пометки элементов?
При добавлении в новый список передавайте пометку третьим параметром: Результат.Добавить(Элемент.Значение, Элемент.Представление, Элемент.Пометка). Способ со свёрткой таблицы пометки не сохраняет — используйте соответствие или перебор.
Что быстрее на большом списке — соответствие или таблица значений?
Оба варианта существенно быстрее перебора с НайтиПоЗначению. Свёртка таблицы выполняется встроенными механизмами платформы и обычно выигрывает на очень больших объёмах, а соответствие удобнее, когда нужно сохранить порядок и представления.
Как убрать дубли строк без учёта регистра?
Перед сравнением приведите строки к одному виду: используйте в качестве ключа соответствия выражение ВРег(СокрЛП(Элемент.Значение)). Тогда «Контрагент» и «контрагент» будут признаны одним значением, а в итоговый список попадёт первый встреченный вариант написания.
Можно ли удалить дубли прямо в запросе, не собирая список?
Да, и это предпочтительный путь. Используйте ВЫБРАТЬ РАЗЛИЧНЫЕ для уникальных строк результата или СГРУППИРОВАТЬ ПО по нужному полю. Затем загрузите результат в список через ЗагрузитьЗначения(Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Поле")).
Почему после очистки в цикле часть дублей осталась?
Скорее всего, элементы удалялись из списка во время прямого перебора по индексу: после каждого удаления индексы сдвигаются, и следующий элемент пропускается. Удаляйте в обратном порядке — от последнего индекса к первому — или собирайте новый список без дублей.
Что важно запомнить
Встроенной дедупликации у списка значений нет. Для больших списков используйте свёртку таблицы значений или соответствие, для малых — перебор с НайтиПоЗначению. Сохраняйте представления и пометки явно, не удаляйте элементы в прямом цикле, нормализуйте строки перед сравнением, а лучше — устраняйте дубли ещё в запросе через РАЗЛИЧНЫЕ или группировку.