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

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

В процессе разработки игровых механик или специфических бизнес-задач на платформе 1С:Предприятие часто возникает необходимость внести элемент случайности. Это может быть как простое получение произвольного числа в заданном диапазоне, так и сложная логика распределения объектов (например, героев, призов или задач) между участниками процесса без повторений. В рамках данной статьи мы подробно разберем, как использовать встроенные инструменты 1С для работы с рандомом, проанализируем различные подходы к архитектуре кода и изучим продвинутые методы оптимизации.

Базовые принципы работы с объектом ГенераторСлучайныхЧисел

Для генерации псевдослучайных последовательностей в языке 1С предусмотрен системный объект ГенераторСлучайныхЧисел. Рассмотрим простейший пример его использования, когда нам нужно просто получить число в интервале от 0 до 100. Это фундамент, на котором строятся все остальные алгоритмы.

Разберем код инициализации и вывода чисел:


Генератор = Новый ГенераторСлучайныхЧисел; 
Для Ном = 1 По 10 Цикл 
    СлучайноеЗначение = Генератор.СлучайноеЧисло(0, 100);
    Сообщить(СлучайноеЗначение); 
КонецЦикла;

В данном примере мы создаем экземпляр объекта Генератор и в цикле вызываем его метод СлучайноеЧисло(). Важно понимать, что параметры метода определяют нижнюю и верхнюю границы включительно. Если нам нужно моделировать, например, бросок игрального кубика, мы будем использовать диапазон (1, 6).

Важный нюанс инициализации: по умолчанию конструктор Новый ГенераторСлучайныхЧисел() использует текущее системное время в качестве начального значения (seed). Это гарантирует, что при каждом запуске программы последовательность будет уникальной. Однако, если передать в конструктор конкретное число, например Новый ГенераторСлучайныхЧисел(42), то последовательность чисел будет строго детерминированной — она будет повторяться при каждом выполнении кода. Это крайне полезно при отладке алгоритмов или для создания "сидов" игрового мира, как в процедурно-генерируемых системах.

Случайный выбор элементов из справочника по коду

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

Проанализируем ситуацию: допустим, у нас ровно 100 героев, и их коды соответствуют числам от 1 до 100. Мы можем сгенерировать число и найти объект следующим образом:


Генератор = Новый ГенераторСлучайныхЧисел;
РандомноеЧисло = Генератор.СлучайноеЧисло(1, 100);
ВыбранныйГерой = Справочники.Герои.НайтиПоКоду(РандомноеЧисло);

Если НЕ ВыбранныйГерой.Пустая() Тогда
    Сообщить("Выпал герой: " + ВыбранныйГерой.Наименование);
КонецЕсли;

Хотя этот метод кажется простым, у него есть существенные недостатки. В реальных базах данных элементы справочников могут удаляться, из-за чего в нумерации кодов возникают «дырки». В таком случае НайтиПоКоду() вернет пустую ссылку. Для решения этой проблемы лучше использовать работу с массивами или прямые запросы к базе данных.

Алгоритм распределения элементов без повторений

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

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

  1. Сначала получим полный список доступных героев из базы данных с помощью запроса.
  2. Выгрузим результат запроса в массив.
  3. В цикле по игрокам будем выбирать случайный индекс элемента массива.
  4. После назначения героя игроку, удалим этот элемент из массива, чтобы он не мог выпасть снова.

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


// Шаг 1: Получаем список героев
ЗапросГерои = Новый Запрос("ВЫБРАТЬ Ссылка ИЗ Справочник.Герои"); 
МассивГероев = ЗапросГерои.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка"); 

// Шаг 2: Получаем список игроков
ЗапросИгроки = Новый Запрос("ВЫБРАТЬ Ссылка ИЗ Справочник.Игроки"); 
МассивИгроков = ЗапросИгроки.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка"); 

СоответствиеНазначений = Новый Соответствие; 
ГСЧ = Новый ГенераторСлучайныхЧисел; 

// Шаг 3: Распределяем героев
Для Каждого Игрок Из МассивИгроков Цикл 
    Если МассивГероев.Количество() = 0 Тогда 
        Прервать; // Герои закончились
    КонецЕсли;

    // Генерируем индекс от 0 до верхней границы массива
    Индекс = ГСЧ.СлучайноеЧисло(0, МассивГероев.ВГраница()); 
    
    // Назначаем героя
    СоответствиеНазначений.Вставить(Игрок, МассивГероев[Индекс]); 
    
    // Удаляем героя из списка доступных, чтобы исключить повтор
    МассивГероев.Удалить(Индекс); 
КонецЦикла;

Этот подход гарантирует уникальность каждого выбора. Стоит отметить, что при работе с очень большими массивами (десятки тысяч элементов) удаление из середины может замедлить работу. В таких случаях эффективнее использовать алгоритм перемешивания Фишера-Йетса, где мы один раз перемешиваем весь массив, а затем просто берем элементы по порядку.

Продвинутые методы: Рандом через запросы и веса

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

Сначала мы получаем общее количество записей в справочнике с помощью Запрос.Выполнить().Количество() или агрегатной функции КОЛИЧЕСТВО(*). Затем генерируем случайное число-смещение. После этого выполняем второй запрос, используя конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 и пропуская сгенерированное количество строк.


КоличествоГероев = 100; // Можно получить запросом
СлучайныйОфсет = ГСЧ.СлучайноеЧисло(0, КоличествоГероев - 1);

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

// В 1С версии 8.3.10+ можно использовать параметры в ПРОПУСТИТЬ
// Если версия ниже, текст запроса формируется динамически
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ВЫБРАТЬ ПЕРВЫЕ 1", "ВЫБРАТЬ ПЕРВЫЕ 1 ПРОПУСТИТЬ " + Формат(СлучайныйОфсет, "ЧГ=0"));

Результат = Запрос.Выполнить();

Вероятностный (взвешенный) рандом: В играх часто требуется, чтобы «Обычные» герои выпадали чаще, чем «Легендарные». Для этого каждому герою в справочнике добавляется числовой реквизит Вес. Алгоритм выбора в таком случае меняется: мы суммируем все веса, генерируем число от 1 до общей суммы и ищем, в какой «диапазон» весов попало наше число. Это позволяет создавать гибко настраиваемые системы выпадения лута или персонажей.

Ограничения и безопасность

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

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

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

← На главную