Массив в 1С: объявление, наполнение, перебор и поиск значений

· 9 мин

Содержание

Массив в 1С создают одной строкой: Массив = Новый Массив;. Элементы добавляют методом Добавить(), читают по индексу Массив[0], перебирают через Для Каждого. Нумерация начинается с нуля, и это первая причина ошибки «Индекс выходит за границы массива» у тех, кто пришёл из других языков.

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

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

Как объявить и создать массив

Есть три способа получить массив, и у каждого своя задача.

Пустой массив. Самый частый случай: размер заранее неизвестен.

Массив = Новый Массив;

Массив заданного размера. Все ячейки создаются сразу и содержат Неопределено.

Массив = Новый Массив(5);   // индексы 0..4
Массив[0] = "Первый";
Массив[4] = 100;

Массив из другой коллекции. Конструктор принимает фиксированный массив и делает изменяемую копию:

Копия = Новый Массив(ФиксированныйМассив);

Обратное преобразование нужно, когда массив отдают наружу и не хотят, чтобы его меняли:

Защищённый = Новый ФиксированныйМассив(Массив);

У фиксированного массива нет Добавить(), Удалить() и записи по индексу. Прочитать и перебрать его можно, изменить нельзя.

Нужен список из трёх известных строк? Не пишите три вызова Добавить. Строка СтрРазделить("А,Б,В", ",") вернёт готовый массив за один вызов.

Основные свойства и методы

МетодЧто делаетЧто возвращает
Добавить(Значение)Дописывает элемент в конецНичего
Вставить(Индекс, Значение)Вставляет элемент, остальные сдвигаются вправоНичего
Установить(Индекс, Значение)Заменяет значение в ячейкеНичего
Получить(Индекс)Читает значение, то же, что Массив[Индекс]Значение
Удалить(Индекс)Убирает элемент, остальные сдвигаются влевоНичего
Найти(Значение)Ищет значение переборомИндекс или Неопределено
Количество()Число элементовЧисло
ВГраница()Последний допустимый индексЧисло, у пустого массива -1
Очистить()Удаляет все элементыНичего

Запомните связку: ВГраница() = Количество() - 1. Для пустого массива это -1, и цикл Для Сч = 0 По Массив.ВГраница() на нём просто не выполнится.

Добавление, вставка и удаление элементов

Добавление в конец:

Товары = Новый Массив;
Товары.Добавить("Молоко");
Товары.Добавить("Хлеб");
Товары.Добавить("Сыр");
// Товары: Молоко, Хлеб, Сыр

Вставка в нужную позицию сдвигает всё, что правее:

Товары.Вставить(1, "Масло");
// Молоко, Масло, Хлеб, Сыр

Замена и удаление по индексу:

Товары.Установить(0, "Кефир");  // Кефир, Масло, Хлеб, Сыр
Товары.Удалить(2);              // Кефир, Масло, Сыр

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

Чтобы удалить элемент по значению, сначала найдите его индекс:

Индекс = Товары.Найти("Масло");
Если Индекс <> Неопределено Тогда
    Товары.Удалить(Индекс);
КонецЕсли;

Проверка на Неопределено обязательна. Если значения нет, Найти() вернёт Неопределено, и Удалить(Неопределено) упадёт с ошибкой.

Массив нельзя заполнять «вслепую» по индексу. Массив = Новый Массив; Массив[0] = 1; завершится ошибкой выхода за границы: ячейки ещё нет. Либо Добавить(), либо массив с заданным размером.

Перебор массива в цикле

Два основных способа и один особый случай.

Для Каждого. Подходит, когда индекс не нужен:

Пример целиком, сумма элементов:

Числа = Новый Массив;
Числа.Добавить(10);
Числа.Добавить(25);
Числа.Добавить(5);

Сумма = 0;
Для Каждого Элемент Из Числа Цикл
    Сумма = Сумма + Элемент;
КонецЦикла;
// Сумма = 40

Цикл по индексу. Нужен, когда важна позиция или надо менять элементы:

Для Сч = 0 По Числа.ВГраница() Цикл
    Числа[Сч] = Числа[Сч] * 2;
КонецЦикла;
// 20, 50, 10

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

Удаление внутри цикла

Это место, где ошибаются чаще всего. При удалении элемента все следующие сдвигаются влево, и прямой цикл пропускает соседа. Выход: идти с конца. Границу фиксируем до цикла, потому что после удаления ВГраница() уменьшится.

Граница = Числа.ВГраница();
Для Сч = 0 По Граница Цикл
    Индекс = Граница - Сч;
    Если Числа[Индекс] < 20 Тогда
        Числа.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;
// остался только 50

Удаление элемента с большим индексом не сдвигает те, что левее. Поэтому оставшиеся индексы в цикле остаются верными.

Не вызывайте Удалить() внутри Для Каждого по тому же массиву. Результат непредсказуем: можно пропустить элементы или получить ошибку. Лучше собрать нужные значения в новый массив и работать с ним.

Поиск значения и отбор элементов

Метод Найти

Найти() обходит массив с начала до первого совпадения и возвращает индекс:

Города = Новый Массив;
Города.Добавить("Москва");
Города.Добавить("Казань");
Города.Добавить("Омск");

Позиция = Города.Найти("Казань");   // 1
Нет = Города.Найти("Тверь");        // Неопределено

Сравнение идёт по значению. Для строк регистр учитывается: "казань" не найдёт "Казань". Если регистр неважен, приведите обе стороны к одному виду функцией ВРег() при заполнении.

Отбор по условию

Готового метода «выбрать подходящие» у массива нет. Делается цикл и второй массив:

Крупные = Новый Массив;
Для Каждого Сумма Из МассивСумм Цикл
    Если Сумма > 1000 Тогда
        Крупные.Добавить(Сумма);
    КонецЕсли;
КонецЦикла;

Уникальные значения

Метода «убрать дубли» тоже нет. Рабочий вариант через Соответствие, ключи которого уникальны:

Уникальные = Новый Массив;
Видели = Новый Соответствие;
Для Каждого Элемент Из Исходный Цикл
    Если Видели[Элемент] = Неопределено Тогда
        Видели[Элемент] = Истина;
        Уникальные.Добавить(Элемент);
    КонецЕсли;
КонецЦикла;

Порядок элементов сохраняется: первое вхождение остаётся на своём месте.

Сортировка

У массива нет метода Сортировать(). Пересыпьте значения в СписокЗначений и вызовите СортироватьПоЗначению(), либо в ТаблицаЗначений с Сортировать("Колонка"), затем выгрузите обратно через ВыгрузитьЗначения() или ВыгрузитьКолонку().

Массив, строки и запросы

Две функции закрывают большую часть задач с текстом.

Части = СтрРазделить("Иванов;Петров;Сидоров", ";");
// Части: Иванов, Петров, Сидоров

Строка = СтрСоединить(Части, ", ");
// "Иванов, Петров, Сидоров"

У СтрРазделить() есть третий параметр: включать ли пустые строки. По умолчанию включаются. Строка "а;;б" даст три элемента, средний пустой. Если пустые не нужны, передайте Ложь.

СтрСоединить() принимает массив строк. Если в массиве числа, предварительно превратите их в строки, например через Формат(Число, "ЧГ=0"): без этого получите ошибку преобразования типа.

Массив и запрос

Массив — штатный способ передать в запрос список значений для условия В:

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

Обратная задача — получить массив из результата запроса. Выгрузите результат в таблицу значений и возьмите колонку:

Таблица = Запрос.Выполнить().Выгрузить();
Ссылки = Таблица.ВыгрузитьКолонку("Ссылка");

Один вызов вместо цикла с Добавить().

Многомерные и вложенные массивы

Массив можно создать сразу с несколькими измерениями:

Сетка = Новый Массив(3, 2);   // 3 строки, 2 столбца
Сетка[0][0] = "А1";
Сетка[0][1] = "Б1";
Сетка[2][1] = "Б3";

Индексы пишутся подряд в квадратных скобках: первый выбирает «строку», второй «столбец». Все ячейки сразу пустые (Неопределено).

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

Группы = Новый Массив;

Первая = Новый Массив;
Первая.Добавить("Иванов");
Первая.Добавить("Петров");

Вторая = Новый Массив;
Вторая.Добавить("Сидоров");

Группы.Добавить(Первая);
Группы.Добавить(Вторая);

Для Каждого Группа Из Группы Цикл
    Для Каждого Фамилия Из Группа Цикл
        Сообщить(Фамилия);
    КонецЦикла;
КонецЦикла;

Вложенный массив хранится как ссылка. Если добавить в Группы один и тот же объект Первая дважды, изменение через один элемент будет видно в другом. Для независимой копии создавайте новый массив на каждом шаге цикла.

Когда у «строки» появляются имена полей, массивы пора менять на Структуру или ТаблицаЗначений. Код вида Данные[2][1] через месяц никто не прочитает.

Пример: сколько раз встречается значение

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

Коды = СтрРазделить("12,7,12,3,7,12", ",");

Счётчики = Новый Соответствие;
Для Каждого Код Из Коды Цикл
    Текущее = Счётчики[Код];
    Счётчики[Код] = ?(Текущее = Неопределено, 1, Текущее + 1);
КонецЦикла;

Для Каждого Пара Из Счётчики Цикл
    Сообщить(Пара.Ключ + ": " + Пара.Значение);
КонецЦикла;

Результат: код 12 встретился 3 раза, 7 — 2 раза, 3 — 1 раз. Числовое значение внутри строки сообщения приводится к строке автоматически, но если нужен формат без пробелов-разделителей тысяч, используйте Формат(Пара.Значение, "ЧГ=0").

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

Что выбрать вместо массива

Массив не всегда лучший вариант. Короткая шпаргалка:

ЗадачаКоллекция
Просто перечень значенийМассив
Перечень с представлением для выбора пользователемСписокЗначений
Быстрый поиск по ключуСоответствие
Набор именованных полейСтруктура
Строки с колонками, сортировка, отбор, свёрткаТаблицаЗначений

Скорость и аккуратность в больших массивах

Несколько практических правил.

  1. Поиск в цикле по большому массиву даёт квадратичное время. Если для каждого из 50 000 элементов вызывается Найти() по другому массиву такого же размера, это миллиарды сравнений. Переложите искомые значения в Соответствие и проверяйте по ключу: доступ почти мгновенный.
  2. Выносите Количество() из условий. В цикле Для Сч = 0 По Массив.ВГраница() граница считается один раз, это нормально. Но в Пока её лучше сохранить в переменную.
  3. Не копируйте массив без нужды. При передаче в процедуру копия не создаётся: параметр хранит ссылку, и изменения внутри процедуры видны снаружи. Если этого не хотите, сделайте копию: Новый Массив(Новый ФиксированныйМассив(Исходный)).
  4. Через границу клиент-сервер массив копируется целиком. Передавать на сервер массив из сотен тысяч элементов ради одного поиска невыгодно. Ищите на сервере и возвращайте результат.
  5. Не используйте массив как таблицу. Если вы по номеру индекса «помните», что в [0] лежит код, а в [1] сумма, пора брать структуру.

Ловушки

  • Ошибка «Индекс выходит за границы массива»: обращение к элементу с индексом, равным Количество(), или к пустому массиву. Перед чтением проверяйте Массив.Количество() > 0.
  • Сравнение массивов оператором = сравнивает ссылки, а не содержимое. Два одинаковых по наполнению массива окажутся «разными».
  • Найти() различает Неопределено и пустую ссылку: это разные значения. Если массив заполнялся из запроса, ищите именно то, что туда попало.
  • Пустое значение в СтрСоединить() даёт пустую подстроку, и в результате появятся лишние разделители. Уберите пустые элементы до соединения.

Частые вопросы

С какого индекса начинается массив в 1С?
С нуля. Первый элемент — Массив[0], последний — Массив[Массив.ВГраница()].
Как узнать, есть ли значение в массиве?
Вызовите Найти(). Если результат не Неопределено, значение есть, а результат — его индекс.
Как очистить массив?
Метод Очистить() удаляет все элементы. Можно и создать новый массив, но тогда старые ссылки на него останутся указывать на прежний объект.
Как превратить массив в строку?
Функцией СтрСоединить(Массив, Разделитель). Элементы должны быть строками.
Чем массив отличается от списка значений?
У списка значений есть представление, пометка и пиктограмма, он удобен для выбора пользователем. Массив проще и быстрее, но хранит только значения.
Можно ли изменить фиксированный массив?
Нет. Создайте обычный массив конструктором Новый Массив(ФиксированныйМассив), измените его и при необходимости снова зафиксируйте.