Основа для создания функционала шансовой выдачи/получения чего либо

Gaikotsu

яжпрограммист
Легенда
Победитель в номинации 2023
Победитель в номинации 2022
Победитель в номинации 2021
Участник Новогоднего Фонда 2021
Эксперт
Знаток
Просветитель
Магистр реакций
Знаток письма
Куратор Данных
Медаль Благодарности
Старожил II степени
Старожил I степени
Победитель в номинации 2020
Победитель в номинации 2019
Клиент разработчик
Преподаватель
За веру и верность форуму
Победитель в номинации 2018
Медаль за активность на Форуме
За заслуги перед форумом
Web разработчик
Разработчик
За знание датапака
За знание ядра
Сообщения
1 814
Розыгрыши
0
Решения
30
Репутация
6 257
Реакции
2 915
Баллы
2 188
Хроники
  1. Prelude
Исходники
Присутствуют
Сборка
любая
Простой базовый класс для создания шансовых списков, элементы из которого можно получить соответственно их шансу, заданному при добавлении элемента в этот список.
За основу была взята идея из класса RndSelector в овере.

Работа с классом проще некуда - объявляем список с нужным типом элементов, заполняем его и после чего можем выбирать из него случайные элементы соответственно их шансам.
Подходит для любой сборки - максимум что придется по необходимости поправить - это импорт для класса Rnd (можно вообще без него обойтись, если задействовать какой нибудь стандартный класс получения рандомных значений).
В данной реализации достигается точность шанса до 6 знаков после запятой. Если необходимо увеличить/уменьшить точность, то необходимо поправить значение переменной MAX_CHANCE и значение SCALE для scaleByPowerOfTen в методе add(E value, String chance)

Java:
package l2p.commons.collections.chance;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.math.BigDecimal;

import l2p.commons.util.Rnd;

/**
 * @author Gaikotsu
 */
public class ChanceList<E> implements Iterable<E>
{
    public static final int MAX_CHANCE = 100_000_000; // Значение, эквивалентное шансу в 100%
    public static final int PER_FACTOR = MAX_CHANCE / 100; // Значение, эквивалентное шансу в 1%
    public static final int SCALE = 6; // На 10 в какой степени умножать значение шанса в варианте с получением его из строки через тип BigDecimal

    private final List<ChanceNode<E>> _nodes;

    private int _totalChance = 0;

    /**
     * Создает пустой список с размером начального списка элементов по умолчанию
     */
    public ChanceList()
    {
        _nodes = new ArrayList<>();
    }

    /**
     * Создает пустой список с заданным начальным размером списка элементов
     *
     * @param capacity - размер
     */
    public ChanceList(int capacity)
    {
        _nodes = new ArrayList<>(capacity);
    }

    /**
     * Добавляет новый элемент в список с заданным шансом
     *
     * @param value - добавляемый элемент
     * @param chance - шанс получения этого элемента
     */
    public boolean add(E value, int chance)
    {
        if (value == null || chance <= 0)
            return false;

        _totalChance += chance;
        return _nodes.add(new ChanceNode<>(value, chance));
    }

    /**
     * Добавляет новый элемент в список с заданным шансом
     *
     * @param value - добавляемый элемент
     * @param chance - шанс получения этого элемента в процентах
     */
    public boolean add(E value, double chance)
    {
        return add(value, (int) (chance * PER_FACTOR));
    }

    /**
     * Добавляет новый элемент в список с заданным шансом
     *
     * @param value - добавляемый элемент
     * @param chance - шанс получения этого элемента в процентах
     */
    public boolean add(E value, String chance)
    {
        return add(value, new BigDecimal(chance).scaleByPowerOfTen(SCALE).intValue());
    }

    /**
     * Удаляет заданный элемент из списка
     *
     * @param value - удаляемый элемент
     */
    public boolean remove(E value)
    {
        if (_nodes.removeIf(node -> node.getValue().equals(value)))
        {
            calcTotalChance();
            return true;
        }

        return true;
    }

    /**
     * Возвращает элемент из списка по его индексу
     */
    public E get(int index)
    {
        return index >= 0 && index < _nodes.size() ? _nodes.get(index).getValue() : null;
    }

    /**
     * Возвращает случайный элемент из списка (вероятность получения зависит от его шанса)
     */
    public E get()
    {
        int chance = Rnd.get(1, _totalChance);
        int current = 0;

        for (ChanceNode<E> node : _nodes)
        {
            current += node.getChance();

            if (current >= chance)
                return node.getValue();
        }

        return null;
    }

    /**
     * Возвращает все элементы из списка
     */
    public List<E> getAll()
    {
        return _nodes.stream().map(node -> node.getValue()).collect(Collectors.toUnmodifiableList());
    }

    /**
     * Возвращает суммарный шанс всех элементов из списка
     */
    public int getTotalChance()
    {
        return _totalChance;
    }

    /**
     * Подсчитывает суммарный шанс всех элементов в списке
     */
    public void calcTotalChance()
    {
        _totalChance = _nodes.stream().mapToInt(node -> node.getChance()).sum();
    }

    /**
     * Возвращает суммарный шанс всех элементов из списка в виде процента
     */
    public double getCurrentChance()
    {
        return _totalChance / (double) PER_FACTOR;
    }

    /**
     * Проверяет, равен ли суммарный шанс значению, эквивалентному шансу в 100%
     */
    public boolean isValid()
    {
        return _totalChance == MAX_CHANCE;
    }

    public boolean isEmpty()
    {
        return _nodes.isEmpty();
    }

    public int size()
    {
        return _nodes.size();
    }

    public void clear()
    {
        _totalChance = 0;
        _nodes.clear();
    }

    @Override
    public Iterator<E> iterator()
    {
        return getAll().iterator();
    }
}
Java:
package l2p.commons.collections.chance;

public class ChanceNode<T>
{
    private final T _value;
    private final int _chance;

    public ChanceNode(T value, int chance)
    {
        _value = value;
        _chance = chance;
    }

    public T getValue()
    {
        return _value;
    }

    public int getChance()
    {
        return _chance;
    }
}

Пример использования
Java:
ChanceList<Integer> chances = new ChanceList<>(); // создаем список с элементами типа Integer
chances.add(1, 1_000_000); // добавляем значение 1 с шансом 1% (1000000 = 1%)
chances.add(2, 49.0); // добавляем значение 2 с шансом 49%
chances.add(3, "50.0"); // добавляем значение 3 с шансом 50%
int value = chances.get(); // получаем случайное значение из списка
 
Последнее редактирование:

private final int _chance;
public int getChance();

Ты наверное хотел E вместо инта
 
А, ну по сути там второй параметр в объявлении класса ChanceNode избыточен - все равно далее не используется.
Код классов подкорректировал.
 
А еще я бы основной add приватным сделал, чтоб все публичные по одной шкале принимали, а то не очевидно)
Заберу пожалуй, как раз хотел рыбалку поправить, а то там кошмар у нас)
 
Ну я у себя просто чаще как раз основной и использую, ну и вариант с передачей в виде строки.

+ он может вызываться в наследных классах, где объявлены дополнительные варианты добавления элементов, например
Java:
package l2p.gameserver.holders.items;

import l2p.commons.collections.chance.ChanceList;

public class ItemGroup extends ChanceList<ItemData>
{
    public void add(ItemData item)
    {
        add(item, item.getChance());
    }
}

Вариант с double вобще не рекомендую использовать в случаях малых шансов или когда надо чтобы сумма шансов гарантировано была равна 100%. Ибо реальный шанс может чуток исказить из-за погрешности вносимой этим типом. Ну т.е. задали к примеру 0.1, а реально там будет например 0.099999 или 0.100001 в итоге.
 
Последнее редактирование:
Красиво, но кое-что так и хочется прямо обернуть под try/catch )
 
Однажды тоже сделал что-то подобное, правда на котлине.
Нужно наследовать модельку от интерфейса и использовать ext метод для получения случайного элемента
Java:
fun <T: WithProbability> Collection<T>.randomWithProbabilities(): T? {
    val totalProbability = sumOf { abs(it.probability) }
    var cumulativeProbability = 0.0
    val randomValue = Rnd.get(0.0, totalProbability)
    return if (totalProbability > 0.0) {
        firstOrNull {
            cumulativeProbability += it.probability
            randomValue <= cumulativeProbability
        }
    } else {
        null
    }
}

interface WithProbability {
    val probability: Double
}
 
Назад
Сверху Снизу