• 游客, 欢迎您来到九域资源社区,如果您是新人,请前往 论坛公告 板块查看新人引导教程 或者 点我打开
    如果您发现没有下载许可, 请先验证邮箱再进行下载;金锭可通过每日登陆或资源出售获取,目前没有其他渠道可获取。

随机实用工具类详解

Real200000

Lv.1 泥土
2023-07-16
2
7
0
前言:
RandomGenerator 出自我以前的项目 Advanced Wish,此项目已废弃不再维护,并改为闭源,在隔壁某论坛有帖子,在此就不放链接了。

应该,可能,有一些大佬听过这个项目,我的荣幸()

依赖:
Lombok / Apache commons math3
(偷懒的好工具 Lombok)

源码:
Java:
package twomillions.plugin.advancedwish.utils.random;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import twomillions.plugin.advancedwish.annotations.JsInteropJavaType;
import twomillions.plugin.advancedwish.utils.texts.QuickUtils;
import org.apache.commons.math3.random.MersenneTwister;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadLocalRandom;

/**
* 一个简单的工具类,用于根据指定的概率随机返回一个对象。
*
* @author 2000000
* @date 2023/2/8
*
* @param <T> 随机对象类型
*/
@NoArgsConstructor
public class RandomGenerator<T> {
    /**
     * 存储所有随机对象及其对应的概率。
     */
    private final ConcurrentLinkedQueue<RandomObject<T>> randomObjects = new ConcurrentLinkedQueue<>();

    /**
     * 所有随机对象的总概率。
     */
    private int totalProbability;

    /**
     * 随机数生成器 ThreadLocalRandom。
     */
    private final ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();

    /**
     * 随机数生成器 SecureRandom。
     */
    private final SecureRandom secureRandom = new SecureRandom();

    /**
     * 通过可选参数创建一个 RandomGenerator 实例。
     *
     * @param values 由对象和概率值组成的数组,不能为空,长度必须为偶数
     * @throws IllegalArgumentException 如果概率值不是整数或缺少概率值,则抛出该异常
     */
    @SafeVarargs
    public RandomGenerator(T... values) {
        for (int i = 0; i < values.length; i += 2) {
            if (i + 1 >= values.length) {
                throw new IllegalArgumentException("Missing probability value for object: " + values[i]);
            }

            T object = values[i];
            Object probabilityValue = values[i + 1];

            if (!QuickUtils.isInt(probabilityValue.toString())) {
                throw new IllegalArgumentException("Probability value for object " + values[i] + " is not an integer.");
            }

            addRandomObject(object, (int) Double.parseDouble(probabilityValue.toString()));
        }
    }

    /**
     * 向工具类中添加一个随机对象及其对应的概率。
     *
     * @param object 随机对象
     * @param probability 对应的概率
     */
    public void addRandomObject(T object, int probability) {
        if (probability <= 0) {
            return;
        }

        RandomObject<T> randomObject = new RandomObject<>(object, probability);

        randomObjects.add(randomObject);
        totalProbability += probability;
    }

    /**
     * 对随机进行检查。
     *
     * @throws IllegalArgumentException 如果没有可供随机的项目或总概率为 0
     */
    private void doSecurityCheck() {
        if (randomObjects.size() == 0) {
            throw new IllegalArgumentException("No objects to randomize!");
        }

        if (totalProbability <= 0) {
            throw new IllegalArgumentException("Random probability of error, totalProbability: " + totalProbability);
        }
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用普通的伪随机数生成器 (PRNG) 生成随机数,它生成的随机数是不可预测的,但不是真正的随机数。这是最常用的。
     *
     * <p>总的来说: 使用普通的伪随机数生成器 (PRNG),效率较高,随机性也不错。适合用于一般场合。
     *
     * @return 随机对象,若没有随机对象则返回 null
     */
    public T getResult() {
        doSecurityCheck();

        int randomNumber = threadLocalRandom.nextInt(totalProbability);
        int cumulativeProbability = 0;

        for (RandomObject<T> randomObject : randomObjects) {
            cumulativeProbability += randomObject.getProbability();
            if (randomNumber < cumulativeProbability) {
                return randomObject.getObject();
            }
        }

        return null;
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用更加安全的随机数生成器,提供更高的随机性和安全性。
     * 使用更多的随机源 (例如系统噪音、硬件事件等) 生成随机数,生成的结果更加随机化。
     *
     * <p>总的来说: 使用安全的随机数生成器,能够生成高质量的随机数。适合用于需要高度安全性和随机性的场合。
     *
     * @return 随机对象,若没有随机对象则返回 null
     */
    public T getResultWithSecureRandom() {
        doSecurityCheck();

        int randomNumber = secureRandom.nextInt(totalProbability);
        int cumulativeProbability = 0;

        for (RandomObject<T> randomObject : randomObjects) {
            cumulativeProbability += randomObject.getProbability();
            if (randomNumber < cumulativeProbability) {
                return randomObject.getObject();
            }
        }

        return null;
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用蒙特卡罗方法生成随机结果,该方法使用的随机性更加均匀和公平。
     * 因为每个对象的概率与它们在队列中出现的次数成正比。这种方法也能够处理大量的对象和概率,性能较低,生成的结果也更加公平。
     *
     * <p>总的来说: 使用蒙特卡罗方法,能够处理大量的对象和概率,性能较低,生成的结果更加公平。适合用于需要高度公平的场合。
     *
     * @return 随机对象
     */
    public T getResultWithMonteCarlo() {
        doSecurityCheck();

        ConcurrentLinkedQueue<T> objects = new ConcurrentLinkedQueue<>();

        for (RandomObject<T> randomObject : randomObjects) {
            for (int i = 0; i < randomObject.getProbability(); i++) {
                objects.add(randomObject.getObject());
            }
        }

        int index = threadLocalRandom.nextInt(objects.size());
        return new ArrayList<>(objects).get(index);
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用随机排序方法将对象列表打乱,洗牌方法。选择第一个对象作为随机结果。
     * 这种方法不太公平,因为它只返回列表中的第一个对象,其他对象的概率更低。此外,此方法的效率也不够高,因为它需要将整个列表随机排序。
     *
     * <p>总的来说: 进行打乱,可以生成相对均匀的分布,效率较低,随机性高,不太公平。适合用于需要高度随机性的场合。
     *
     * @return 随机对象
     */
    public T getResultWithShuffle() {
        doSecurityCheck();

        ConcurrentLinkedQueue<T> objects = new ConcurrentLinkedQueue<>();

        for (RandomObject<T> randomObject : randomObjects) {
            for (int i = 0; i < randomObject.getProbability(); i++) {
                objects.add(randomObject.getObject());
            }
        }

        ArrayList<T> list = new ArrayList<>(objects);

        Collections.shuffle(list);
        return list.get(0);
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用高斯分布随机数生成器,生成的随机数是具有高斯分布的。。
     *
     * <p>总的来说: 使用高斯分布,高斯分布会将随机结果偏向均值,可以在一定程度上避免随机数集中在中间值的问题。适合用于需要正态分布随机数的场合。
     *
     * @return 随机对象,若没有随机对象则返回 null
     */
    public T getResultWithGaussian() {
        doSecurityCheck();

        double mean = totalProbability / 2.0;
        double standardDeviation = totalProbability / 6.0;

        int randomNumber = (int) Math.round(threadLocalRandom.nextGaussian() * standardDeviation + mean);

        randomNumber = Math.max(0, randomNumber);
        randomNumber = Math.min(totalProbability - 1, randomNumber);

        int cumulativeProbability = 0;

        for (RandomObject<T> randomObject : randomObjects) {
            cumulativeProbability += randomObject.getProbability();
            if (randomNumber < cumulativeProbability) {
                return randomObject.getObject();
            }
        }

        return null;
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用 Mersenne Twister 伪随机数生成器生成随机数。该算法具有良好的随机性和周期性。
     * 在所有伪随机数生成算法中,Mersenne Twister 的周期最长。使用此方法生成的随机数具有很高的随机性。
     *
     * <p>总的来说: 使用 Mersenne Twister 随机数生成器,具有良好的随机性和速度。适合用于需要高质量随机数的场合。
     *
     * @return 随机对象,若没有随机对象则返回 null
     */
    public T getResultWithMersenneTwister() {
        doSecurityCheck();

        org.apache.commons.math3.random.RandomGenerator randomGenerator = new MersenneTwister();

        int randomNumber = randomGenerator.nextInt(totalProbability);

        int cumulativeProbability = 0;

        for (RandomObject<T> randomObject : randomObjects) {
            cumulativeProbability += randomObject.getProbability();
            if (randomNumber < cumulativeProbability) {
                return randomObject.getObject();
            }
        }

        return null;
    }

    /**
     * 根据当前所有随机对象的概率,随机返回其中的一个对象。
     *
     * <p>此方法使用 XORShift 伪随机数生成器生成随机数。该算法具有良好的随机性和速度。
     * 在所有伪随机数生成算法中,XORShift 是速度最快的之一,但随机性不如其他算法。
     *
     * <p>总的来说: 使用 XORShift 随机数生成器,速度较快。适合用于需要高效率并且对于随机性要求不高的场合。
     *
     * @return 随机对象,若没有随机对象则返回 null
     */
    public T getResultWithXORShift() {
        doSecurityCheck();

        int y = (int) System.nanoTime();

        y ^= (y << 6);
        y ^= (y >>> 21);
        y ^= (y << 7);

        int randomNumber = Math.abs(y) % totalProbability;

        int cumulativeProbability = 0;

        for (RandomObject<T> randomObject : randomObjects) {
            cumulativeProbability += randomObject.getProbability();
            if (randomNumber < cumulativeProbability) {
                return randomObject.getObject();
            }
        }

        return null;
    }

    /**
     * 表示一个随机对象及其对应的概率。
     *
     * @param <T> 随机对象类型
     */
    @AllArgsConstructor
    private static class RandomObject<T> {
        /**
         * 随机对象。
         */
        private final T object;

        /**
         * 随机对象的概率。
         */
        private final int probability;

        /**
         * 获取随机对象。
         *
         * @return 随机对象
         */
        public T getObject() {
            return object;
        }

        /**
         * 获取随机对象的概率。
         *
         * @return 随机对象的概率
         */
        public int getProbability() {
            return probability;
        }
    }
}

简介:

在本文中,我们将介绍 RandomGenerato 实用工具类,该工具类根据指定的概率随机选择一个对象。
当您需要从一组对象中随机选择一个对象,并且每个对象都有一个相关联的选择概率时,这个工具类将特别方便。我们将详细介绍它的功能、用法,并讨论其优点和局限性。

特点:

灵活的概率分配: RandomGenerator 允许您动态地添加随机对象及其对应的概率。您可以为每个对象指定概率,从而对每个项目的选中概率进行精细调整。
多种随机算法: 该类提供了不同的随机算法,以适应不同的用例。用户可以选择以下算法之一:

ThreadLocalRandom: 使用标准伪随机数生成器,高效且适用于大多数情况。
SecureRandom: 提供更安全的随机数生成器,适用于需要更高安全级别的应用程序。
蒙特卡罗方法: 确保结果公平均匀的分布,但对于较大的对象集可能效率较低。
洗牌方法: 通过打乱对象列表来实现随机化,提供更高的随机性,但可能牺牲公平性。
高斯方法: 生成遵循高斯分布的随机数,可以避免随机数集中在中间值的问题。

用法示例:

以下是一个简单的示例,演示如何使用 RandomGenerator 类来进行随机选择:

Java:
public class Main {
    public static void main(String[] args) {
        RandomGenerator<String> randomGenerator = new RandomGenerator<>(
                "Apple", 30,
                "Banana", 50,
                "Orange", 20
        );

        // 使用普通伪随机数生成器进行随机选择
        String result = randomGenerator.getResult();
        System.out.println("普通伪随机数生成器选择的结果: " + result);

        // 使用更安全的随机数生成器进行随机选择
        String secureResult = randomGenerator.getResultWithSecureRandom();
        System.out.println("更安全的随机数生成器选择的结果: " + secureResult);

        // 使用蒙特卡罗方法进行随机选择
        String monteCarloResult = randomGenerator.getResultWithMonteCarlo();
        System.out.println("蒙特卡罗方法选择的结果: " + monteCarloResult);

        // 使用洗牌方法进行随机选择
        String shuffleResult = randomGenerator.getResultWithShuffle();
        System.out.println("洗牌方法选择的结果: " + shuffleResult);

        // 使用高斯方法进行随机选择
        String gaussianResult = randomGenerator.getResultWithGaussian();
        System.out.println("高斯方法选择的结果: " + gaussianResult);
    }

优点:

灵活性: 允许动态添加随机对象和概率,方便实现复杂的随机选择逻辑。
多样性的随机算法: 提供了多种随机算法,以满足不同场景下的随机需求。
易于使用: 使用简单,不需要繁琐的手动计算概率。

局限性:

性能: 在使用蒙特卡罗方法和洗牌方法时,可能因为需要处理大量的对象和概率而导致性能下降。
公平性: 洗牌方法在某些情况下可能会导致结果不够公平,因为只返回列表中的第一个对象,其他对象的概率较低。

解析实现:

类结构:

RandomGenerator 是一个泛型类,它使用 Java 线程安全的 ConcurrentLinkedQueue 来存储所有随机对象及其对应的概率。每个随机对象都由 RandomObject 类表示,它保存了对象本身和其对应的概率。

构造函数:
RandomGenerator 提供了多个构造函数,允许用户以不同方式初始化随机对象和概率。
RandomGenerator 使用 Lombok @NoArgsConstructor 注解生成无参构造,并允许传入数组一次性添加多个随机对象及其概率。
RandomObject 使用了 @AllArgsConstructor 生成有参构造。

添加随机对象:
使用 addRandomObject(T object, int probability) 方法可以向 RandomGenerator 中添加随机对象和对应的概率。在添加过程中,概率值将累加为 totalProbability,确保所有对象的概率之和。

安全性检查:
doSecurityCheck() 方法在每次随机选择前进行安全性检查。它确保至少有一个可供随机选择的对象,并且总概率不为零。

普通伪随机数生成器:
getResult() 方法使用 ThreadLocalRandom 作为普通的伪随机数生成器,通过 nextInt(totalProbability) 生成随机数。然后根据累积概率找到对应的随机对象并返回。

其他随机算法:
RandomGenerator 还提供了其他随机算法的实现,例如:

getResultWithSecureRandom(): 使用更安全的随机数生成器 SecureRandom 实现随机选择。
getResultWithMonteCarlo(): 使用蒙特卡罗方法生成随机结果,根据对象概率重复添加对象并从中随机选择。
getResultWithShuffle(): 使用洗牌方法将对象列表随机打乱,然后选择第一个对象作为结果。
getResultWithGaussian(): 使用高斯分布随机数生成器,生成符合高斯分布的随机数。

性能和优缺点:
RandomGenerator 提供了多种随机算法的选择,从而在不同场景下取得平衡:

对于一般场合,使用普通的伪随机数生成器性能较高,随机性也不错。
需要高安全性和随机性的场合,可以选择更安全的随机数生成器。
需要高度公平的场合,蒙特卡罗方法是一个合适的选择。
如果对于随机性的要求不高,但追求高效率,可以使用 XORShift 随机数生成器。
然而,蒙特卡罗方法和洗牌方法可能因为需要处理大量的对象和概率而导致性能下降,而洗牌方法可能导致结果不够公平。

结语:
我最近在一些 QQ 群内看到了很多大佬写一些关于随机的插件,所以水一篇教程,水平有限,若有帮助那是极好的。
您可以使用这个类,且如果您想,不必标注原作者 (真的会如此狠心吗( )



Just

DO WHAT THE F$CK U WANT TO
 
最后编辑: