Однажды меня клиент попросил сделать механизм случайно выпадающей скидки на следующую покупку.
Собственно тут в случайном выборе скидки ничего сложного, как вариант - поместить все скидки в массив, и использовать генератор случайных чисел от 0 до размера массива -1, что бы определить нужный индекс элемента этого массива.
Но потом в задаче появилось уточнение - скидки должны выпадать по обратной экспоненциальной зависимости. Т.е. к примеру скидка 5% должна выпадать гораздо чаще скидки 95%.
Желание клиента для меня вполне понятно, нечего постоянно раскидываться большими скидками, но пусть они иногда выпадают. Почему экспоненциально - будем считать что для красоты (я не знаю откуда взялась эта идея).
Опишу тут два варианта решения, которые я нашел для себя.
1. Создать массив скидок, где некоторые скидки повторяются N раз ( N это "вес" числа ).
// Для примера создадим массив чисел (скидок) от 1 до 99
МассивЧисел = Новый Массив();
Счетчик = 1;
Пока Счетчик <= 100 Цикл
Массив.Добавить(Счетчик);
Счетчик = Счетчик + 1;
КонецЦикла;
// На основе массива значений скидок сделаем новый массив, где скидки будут повторятся пропорционально весу значения скидки
МассивПоВесу = СоздатьМассивПоВесу(МассивЧисел);
// Выберем случайное число в диапазоне от 0 до количества элементов массива
ГСЧ = Новый ГенераторСлучайныхЧисел();
СлучайноеЧисло = ГСЧ.СлучайноеЧисло(0, МассивПоВесу.Количество()-1);
// Нашли случайную скидку
СлучайнаяСкидка = МассивПоВесу[СлучайноеЧисло];
Функция СоздатьМассивПоВесу(МассивЧисел)
МассивПоВесу = Новый Массив;
// коэффициент, который показывает во сколько раз скидка 0% больше скидки 100% , можно выбрать по своему желанию
Коэффициент = 20;
Для каждого Элемент Из МассивЧисел Цикл
Вес = Exp( (100 - Элемент) / (100 / Log(Коэффициент)) );
Вес = Окр( Вес * 10, 0); // 10 - в данном случае подобрано эмпирически, для округления до удобного целого числа
Для Счетчик = 1 По Вес Цикл
МассивПоВесу.Добавить(Элемент);
КонецЦикла;
КонецЦикла;
Возврат МассивПоВесу;
КонецФункции
2. Получение скидки сложением весов чисел в цикле.
Для получения случайной скидки суммируем веса скидок, пока эта сумма не превысит случайное число, умноженное на сумму всех весов.
// Для примера создадим массив чисел (скидок) от 1 до 99
МассивЧисел = Новый Массив();
Счетчик = 1;
Пока Счетчик <= 100 Цикл
МассивЧисел .Добавить(Счетчик);
Счетчик = Счетчик + 1;
КонецЦикла;
// Для каждой скидки определим вес и поместим всё в таблицу значений
ТаблицаСкидок = СоздатьТаблицуВесовСкидок(МассивЧисел);
СуммаВесов = 0;
Для каждого СтрокаТЗ Из ТаблицаСкидок Цикл
СуммаВесов = СуммаВесов + СтрокаТЗ.Вес;
КонецЦикла;
// Случайное число 0 .. 1
ГСЧ = Новый ГенераторСлучайныхЧисел();
СлучайноеЧисло = ГСЧ.СлучайноеЧисло(0, 100) / 100;
Лямбда = СуммаВесов * СлучайноеЧисло;
// В цикле накапливаем вес, пока не сработает условие. Текущая строка при этом и будет найденная скидка
Накопление = 0;
Для Каждого СтрокаТЗ Из ТаблицаСкидок Цикл
Накопление = Накопление + СтрокаТЗ.Вес;
Если Накопление >= Лямбда Тогда
СлучайнаяСкидка = СтрокаТЗ.Скидка; // Нашли скидку
Прервать;
КонецЕсли;
КонецЦикла;
Функция СоздатьТаблицуВесовСкидок(Массив)
ТаблицаСкидок = Новый ТаблицаЗначений;
ТаблицаСкидок.Колонки.Добавить("Скидка");
ТаблицаСкидок.Колонки.Добавить("Вес");
Для каждого Элемент Из Массив Цикл
НоваяСтрока = ТаблицаСкидок.Добавить();
НоваяСтрока.Скидка = Элемент;
// коэффициент, который показывает во сколько раз скидка 0% больше скидки 100% , можно выбрать по своему желанию
Коэффициент = 20;
Коэф = 100 / Log(Коэффициент);
НоваяСтрока.Вес = Exp( (100 - Элемент) / Коэф);
КонецЦикла;
// На случай не отсортированного массива на входе
ТаблицаСкидок.Сортировать("Скидка");
Возврат ТаблицаСкидок;
КонецФункции
Ключевые моменты описаны в коде выше.
В обработке есть демонстрация работы обоих алгоритмов и отрисовка графика для метода сложения сумм весов. Кроме этого добавил еще вариант линейной зависимости.
Тестировалось в BAS ERP 2.1.6.3