1C-CodeОсновы 1С → Коллекции: где хранить много значений

Коллекции: где хранить много значений

Массив, соответствие, структура и таблица значений — четыре инструмента и правило, как выбрать нужный.

Глава 10 из 36 · чтение примерно 30 минут

Переменная хранит одно значение. Но данные почти всегда идут пачками: строки документа, список товаров, цены по номенклатуре.

Для этого есть коллекции. Их в 1С четыре основных, и выбор между ними — не дело вкуса: каждая отвечает на свой вопрос быстро, а на чужой — медленно и криво.

Как выбрать за пять секунд

Вопрос, на который отвечают данныеЧто брать
«Просто список, по нему пройдут по порядку»Массив
«Найти значение по ключу: цену по товару»Соответствие
«Несколько именованных полей одного объекта»Структура
«Строки и колонки: сгруппировать, сложить, отсортировать»ТаблицаЗначений

Массив

Упорядоченный список. Элементы идут по порядку, у каждого есть номер — индекс.

Товары = Новый Массив;
Товары.Добавить("Гвозди");
Товары.Добавить("Шурупы");

Товары[0];              // "Гвозди" — индексы с НУЛЯ
Товары.Количество();    // 2
Товары.ВГраница();      // 1 — номер последнего
Товары.Найти("Шурупы"); // 1, а если нет — Неопределено

Индексы с нуля, и за границу выходить нельзя

Первый элемент — Массив[0], а последний — Массив[Массив.Количество() - 1]. Единицу вычесть обязательно.

Обращение за границу — ошибка с прекращением работы, а не пустое значение. Поэтому для обхода почти всегда берут Для Каждого: там выйти за границу невозможно в принципе.

А Найти при неудаче возвращает Неопределено, а не −1 и не 0. Проверять надо через <> Неопределено.

Упражнение 1. Первый массив

Создайте массив с именем Товары и добавьте в него три строки: Гвозди, Шурупы, Молоток. Проверка смотрит на количество элементов.

Решается в тренажёре: код запускается и проверяется сразу.

Соответствие

Пары «ключ → значение». Позволяет мгновенно достать значение по ключу, не перебирая всё подряд.

Цены = Новый Соответствие;
Цены.Вставить("Гвозди", 150);
Цены.Вставить("Шурупы", 80);

Цены["Гвозди"];        // 150
Цены["Молоток"];       // Неопределено — ключа нет
Цены.Количество();     // 2

Соответствие — это ответ на «запрос в цикле»

Помните правило из главы про циклы: к базе обращаются до цикла, а не внутри? Соответствие — то, куда складывают результат.

Один запрос достаёт цены всех нужных товаров сразу. Их раскладывают в соответствие «товар → цена». Дальше цикл берёт цену из памяти за один шаг, вместо запроса к базе на каждой строке.

Именно поэтому соответствие — самая важная коллекция для производительности.

Нет ключа — Неопределено, и оно взорвётся позже

Цены["Молоток"] при отсутствующем ключе вернёт Неопределено. Не ноль, не ошибку.

Проблема в том, что падение случится не здесь, а строкой ниже: Сумма = Количество * Цена; — умножение на Неопределено прекращает работу, и в сообщении будет указана эта строка, а не настоящая причина.

Поэтому значение из соответствия либо проверяют, либо сразу подставляют умолчание:

Цена = Цены[Товар];
Если Цена = Неопределено Тогда
	Цена = 0;
КонецЕсли;

Упражнение 2. Цена с умолчанием

Напишите функцию ЦенаТовара(Цены, Товар): вернуть цену из соответствия, а если такого ключа нет — 0. Проверка спрашивает цену Молотка, которого в списке нет, и ждёт 0.

Решается в тренажёре: код запускается и проверяется сразу.

Как обходят соответствие

В цикле Для Каждого по соответствию переменная получает не значение, а пару. У пары есть .Ключ и .Значение.

Обход пар

Для Каждого Пара Из Цены Цикл
	Сообщить(Пара.Ключ + ": " + Строка(Пара.Значение));
КонецЦикла;
ЧастьЧто означает
Паране значение и не ключ, а пара целиком
.Ключто, по чему ищут — например, товар
.Значението, что нашли — например, цена
порядокна порядок обхода соответствия не стоит рассчитывать: нужен порядок — берите массив или таблицу

Упражнение 3. Общая стоимость склада

Напишите ИтогоПоСкладу(Остатки): сложить все значения соответствия «товар → сумма». Проверка передаёт три позиции на 150, 80 и 200 и ждёт 430.

Решается в тренажёре: код запускается и проверяется сразу.

Структура

Набор именованных полей одного объекта. Имена полей известны заранее, когда пишется код.

Позиция = Новый Структура;
Позиция.Вставить("Товар", "Гвозди");
Позиция.Вставить("Количество", 10);

// или короче, одной строкой:
Позиция = Новый Структура("Товар, Количество", "Гвозди", 10);

Позиция.Товар;          // "Гвозди" — обращение через точку
Позиция.Количество;     // 10

Структура или соответствие — в чём разница

Обе хранят пары «имя → значение». Разница в том, известны ли имена заранее.

Структура — известны. Вы пишете Позиция.Товар прямо в коде, и полей обычно немного. Имя поля обязано быть допустимым именем переменной: без пробелов, не с цифры.

Соответствие — неизвестны. Ключи приходят из данных: товары, склады, даты. Их может быть тысяча, и ключом может быть что угодно — хоть ссылка, хоть дата.

Практическое следствие: «цены по товарам» — всегда соответствие. «Результат проверки: успех и текст ошибки» — всегда структура.

Обращение к несуществующему полю падает

Позиция.Цена, если поля Цена нет, — ошибка «Поле объекта не обнаружено». В отличие от соответствия, которое молча вернёт Неопределено.

Когда структура приходит извне и состав полей не гарантирован, используют Свойство:

Значение = 0;
Если Параметры.Свойство("Цена", Значение) Тогда
	// поле есть, и оно уже лежит в переменной Значение
КонецЕсли;

Метод возвращает Истину или Ложь, а само значение кладёт во вторую переменную.

Упражнение 4. Вернуть два значения сразу

Функция может вернуть только одно значение — но структура считается одним. Соберите структуру с именем Результат и двумя полями: Успех = Истина и Текст = Готово.

Решается в тренажёре: код запускается и проверяется сразу.

ТаблицаЗначений

Строки и колонки, как в таблице. Умеет сама сортировать, группировать и считать итоги.

Т = Новый ТаблицаЗначений;
Т.Колонки.Добавить("Товар");
Т.Колонки.Добавить("Сумма");

СтрокаФайла = Т.Добавить();          // добавили пустую строку
СтрокаФайла.Товар = "Гвозди";
СтрокаФайла.Сумма = 1500;

Т.Итог("Сумма");             // сумма по всей колонке
Т.Сортировать("Сумма Убыв");
Т.Свернуть("Товар", "Сумма");  // сгруппировать и сложить

Свернуть — группировка в одну строку

Т.Свернуть("Товар", "Сумма") схлопывает строки с одинаковым товаром в одну, складывая суммы. Первый аргумент — по чему группировать, второй — что суммировать.

Это делает за одну строку то, на что вручную ушёл бы цикл с соответствием.

Обратите внимание: Свернуть меняет саму таблицу, а не возвращает новую. Как и Сортировать. Здесь коллекции ведут себя не так, как строки, — их методы меняют объект на месте.

Упражнение 5. Таблица и итог

Создайте таблицу Т с колонками Товар и Сумма, добавьте две строки на 1500 и 800. Проверка смотрит на итог по колонке Сумма — ожидается 2300.

Решается в тренажёре: код запускается и проверяется сразу.

Коллекции передаются по ссылке

Это свойство, из-за которого случаются самые загадочные ошибки.

Когда вы передаёте массив в функцию, копии не создаётся. Функция получает тот же самый массив. Добавит она туда элемент — он появится и у вызывающего.

С числом или строкой так не бывает: они копируются.

✓ Число — копия

Процедура Испортить(Ч)
	Ч = 999;
КонецПроцедуры

Число = 5;
Испортить(Число);
// Число по-прежнему 5

Функция работала со своей копией. Снаружи ничего не изменилось.

✕ Массив — тот же объект

Процедура Испортить(М)
	М.Добавить(999);
КонецПроцедуры

Массив = Новый Массив;
Испортить(Массив);
// в Массиве теперь есть 999

Функция изменила не копию, а сам массив вызывающего.

Передача по ссылке — рабочий приём

На передаче по ссылке держится половина типового кода: процедура заполняет переданную таблицу, дополняет структуру параметров, добавляет сообщения в общий список.

Помнить надо одно: если вы приняли коллекцию и меняете её, вы меняете чужие данные. Когда это не входило в замысел, делайте копию — Скопировать() у таблицы значений, или собирайте новый массив вместо правки исходного.

Упражнение 6. Только положительные

Напишите ТолькоПоложительные(Массив): собрать новый массив из чисел больше нуля и вернуть его количество. Исходный массив не менять. Проверка передаёт 5, −3, 0, 8 и ждёт 2.

Решается в тренажёре: код запускается и проверяется сразу.

Ещё две коллекции, без которых не обойтись

Четыре коллекции выше — это то, что есть в языке. Но в рабочем коде постоянно встречаются ещё две конструкции, и без них чужой код не прочитать: список значений и массив структур.

СписокЗначений

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

Склады = Новый СписокЗначений;
Склады.Добавить("Основной", "Основной склад");
Склады.Добавить("Резерв", "Резерв цеха");

Склады[0].Значение;          // "Основной" — что внутри
Склады[0].Представление;     // "Основной склад" — что видит человек
Склады[0].Пометка = Истина;  // галочка

Склады.НайтиПоЗначению("Резерв");  // элемент или Неопределено
Склады.СортироватьПоПредставлению();

Зачем отдельный тип, если есть массив

У массива есть только значения. А форме нужно три вещи сразу: что выбрали (значение), что написать в строке (представление) и отмечено ли (пометка).

Отсюда и разделение. Значение — для программы: ссылка на склад, код, перечисление. Представление — для человека: текст, который он прочитает. Смешивать их — типичная ошибка: в список кладут красивые строки, а потом не могут по ним найти сам объект.

И ещё одно: Добавить у списка значений возвращает добавленный элемент, а не индекс. Поэтому и пишут в одну строку: Склады.Добавить(Склад, "Основной").Пометка = Истина;

Упражнение 7. Отметить нужное

В списке три склада. Пометьте Резерв и Брак — искать их надо по значению, а не по номеру, — и посчитайте помеченные в переменную Отмечено.

Решается в тренажёре: код запускается и проверяется сразу.

Массив структур: таблица, которой не нужна таблица

Самая частая коллекция в типовом коде — не таблица значений, а массив структур. Каждая структура — строка, поля структуры — колонки.

Причина не в красоте. Таблицу значений нельзя передать с сервера на клиент в управляемой форме: это несериализуемый тип. А массив структур передаётся спокойно — поэтому именно им возвращают результаты серверных вызовов.

Строки документа в памяти

Строки = Новый Массив;
Строки.Добавить(Новый Структура("Товар, Количество, Цена", "Гвозди", 10, 150));
Строки.Добавить(Новый Структура("Товар, Количество, Цена", "Шурупы", 3, 80));

Для Каждого Строка Из Строки Цикл
	Сообщить(Строка.Товар + ": " + Строка.Количество * Строка.Цена);
КонецЦикла;
ЧастьЧто означает
Новый Структура("Товар, Количество, Цена", …)имена полей одной строкой через запятую, значения — следом по порядку
Строки.Добавить(…)структура кладётся в массив целиком, копии не делается
Строка.Товаробращение по имени поля — как к колонке таблицы
Строка.Количество * Строка.Ценасчитаем прямо в обходе: коллекция в памяти, база не трогается

Упражнение 8. Сумма по массиву структур

Напишите СуммаСтрок(Строки): каждый элемент — структура с полями Товар, Количество, Цена. Верните сумму «количество × цена» по всем строкам. Проверка передаёт 10×150 и 3×80 и ждёт 1740.

Решается в тренажёре: код запускается и проверяется сразу.

Имя поля структуры — не любая строка

Поле структуры называется по правилам имени переменной: буквы, цифры и подчёркивание, и не с цифры. Пробелов, точек и дефисов быть не может.

Об это спотыкаются, когда имена берут из данных: из колонок загружаемого файла, из названий складов, из кодов. Новый Структура("Склад основной", 5) — ошибка прямо при создании.

Правило простое: имена полей вы придумываете сами, когда пишете код. Если ключи приходят из данных — это соответствие, а не структура. Там ключом может быть что угодно: хоть строка с пробелами, хоть ссылка на справочник.

Менять коллекцию, пока по ней идёшь

Задача звучит безобидно: пройти по массиву и выкинуть лишнее. Написанная в лоб, она ломается — и ломается не всегда, а через раз, в зависимости от данных. Такие ошибки живут в коде годами.

Причина в том, что Удалить сдвигает все следующие элементы на позицию назад. Счётчик при этом идёт вперёд как ни в чём не бывало: один элемент оказывается перепрыгнут, а в конце счётчик уходит за границу — той самой ошибкой с прекращением работы.

✓ Так — пропустит и упадёт

Для Сч = 0 По Массив.ВГраница() Цикл
	Если Массив[Сч] = 0 Тогда
		Массив.Удалить(Сч);
	КонецЕсли;
КонецЦикла;

Верхняя граница посчитана ОДИН раз, до первого удаления. Массив стал короче, а цикл об этом не знает.

✕ Так — с конца, и всё цело

Сч = Массив.ВГраница();
Пока Сч >= 0 Цикл
	Если Массив[Сч] = 0 Тогда
		Массив.Удалить(Сч);
	КонецЕсли;
	Сч = Сч - 1;
КонецЦикла;

Идём справа налево: удаление сдвигает только то, что уже пройдено. Ничего не пропущено, за границу не вышли.

Обратного Для в 1С нет

В большинстве языков у цикла есть шаг: «от последнего к первому, шаг минус один». В 1С шага нет. Для Сч = 5 По 0 Цикл — не ошибка: он просто не выполнится ни разу, молча.

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

Второй способ обойтись без этого вовсе: не удалять, а собирать новый массив из того, что нужно оставить. Читается лучше, и ломаться там нечему — как в упражнении «Только положительные» ниже.

Упражнение 9. Выкинуть нули

Напишите УбратьНули(Массив): удалите из самого массива все нули и верните, сколько элементов осталось. Проверка передаёт 5, 0, 3, 0, 0 и ждёт 2. Обход в лоб здесь падает — идите с конца.

Решается в тренажёре: код запускается и проверяется сразу.

Группировка в памяти: соответствие массивов

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

Устройство: соответствие, где ключ — группа, а значение — массив попавшего в неё. Единственная тонкость в том, что массив для новой группы надо создать, а для уже встреченной — взять существующий.

Разложить по складам

ПоСкладу = Новый Соответствие;

Для Каждого Строка Из Строки Цикл
	Список = ПоСкладу[Строка.Склад];
	Если Список = Неопределено Тогда
		Список = Новый Массив;
		ПоСкладу.Вставить(Строка.Склад, Список);
	КонецЕсли;
	Список.Добавить(Строка.Товар);
КонецЦикла;
ЧастьЧто означает
ПоСкладу[Строка.Склад]берём массив группы; ключа ещё нет — придёт Неопределено
Список = Новый Массивпервая строка этого склада: заводим массив
ПоСкладу.Вставить(…, Список)кладём массив в соответствие ОДИН раз — дальше он уже там
Список.Добавить(Строка.Товар)работает и для нового, и для найденного: массив — один и тот же объект, он лежит в соответствии по ссылке

Почему не надо класть массив обратно

Строчки ПоСкладу.Вставить(Склад, Список); в конце нет — и это не забывчивость. Коллекции хранятся по ссылке: в соответствии лежит не копия массива, а он сам.

Добавили элемент в Список — он появился и в том, что лежит в соответствии, потому что это одно и то же. Та самая передача по ссылке из конца главы, только здесь она работает на нас.

Упражнение 10. Разложить по складам

Напишите ПоСкладам(Строки): каждый элемент — структура с полями Склад и Товар. Разложите товары по складам и верните, сколько товаров на складе «Основной». Проверка передаёт четыре строки, три из них — на Основном.

Решается в тренажёре: код запускается и проверяется сразу.

Скопировать копирует не всё

Скопировать() у таблицы значений и Новый Массив(Исходный) делают копию верхнего уровня. Сам массив новый, а элементы в нём — те же самые объекты.

Для чисел и строк разницы нет: их значения и так копируются. Но если в массиве лежали структуры, копия массива ссылается на те же структуры — правка поля видна в обоих.

Когда нужна копия целиком, структуры создают заново в обходе. Единственная альтернатива — ЗначениеВСтрокуВнутр и обратно, но так делают редко: медленно и подходит не всем типам.

Фиксированные коллекции: почему Добавить падает

Рано или поздно вы получите ошибку «Метод объекта не обнаружен (Добавить)» на массиве, который выглядит как обычный. Это ФиксированныйМассив — коллекция, которую нельзя менять.

Сами вы такие почти не создаёте, а получаете: параметры сеанса, часть свойств метаданных, дополнительные свойства объектов — всё это приходит фиксированным, чтобы один код не испортил данные другому.

Что делать: сделать изменяемую копию. Новый Массив(Фиксированный) и Новый Структура(Фиксированная) дают обычные коллекции с теми же значениями. Обратно — тем же способом: Новый ФиксированныйМассив(Массив).

Массив как параметр запроса

Отдельная причина, по которой массив встречается чаще остальных коллекций: его понимает язык запросов. Это штатный способ спросить у базы «вот про эти двадцать товаров, все сразу».

Один запрос вместо двадцати

Запрос.Текст =
"ВЫБРАТЬ Ссылка, Цена ИЗ Справочник.Номенклатура
|ГДЕ Ссылка В (&СписокТоваров)";

Запрос.УстановитьПараметр("СписокТоваров", Товары);
ЧастьЧто означает
В (&СписокТоваров)условие «входит в список» — в параметр кладут массив
Товарыобычный массив ссылок; список значений и таблица значений тоже подойдут
одно обращениевместо запроса в цикле на каждый товар — то самое правило из главы про циклы

Пустой массив в параметре

Ссылка В (&Список) с пустым массивом не находит ничего — и это правильно, но неожиданно: код, который «работал», однажды тихо возвращает пустой результат, потому что список наверху оказался пуст.

Поэтому перед запросом почти всегда стоит Если Товары.Количество() = 0 Тогда Возврат; КонецЕсли;. Это не перестраховка: это разница между «ничего не нашли» и «нечего было искать».

Шпаргалка по коллекциям

ДействиеМассивСоответствиеТаблицаЗначений
создатьНовый МассивНовый СоответствиеНовый ТаблицаЗначений
добавитьМ.Добавить(З)С.Вставить(К, З)СтрокаФайла = Т.Добавить()
получитьМ[0]С[Ключ]Т[0].Колонка
сколькоМ.Количество()С.Количество()Т.Количество()
убратьМ.Удалить(Индекс)С.Удалить(Ключ)Т.Удалить(Стр)
если нетвыход за границу — ошибкаНеопределеноНайтиСтроки даёт пустой массив
обойтиДля Каждого Э Из МДля Каждого Пара Из СДля Каждого СтрокаФайла Из Т

Что дальше

Коллекции — последняя тема «чистого языка». Дальше начинается сама 1С: справочники и документы, чтение и запись данных, программная работа с формами.

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

Циклы: повторить для каждого
Справочники и документы: чтение и запись