Java 数组列表中具有不同机会的随机元素

Java 数组列表中具有不同机会的随机元素,java,Java,正如在标题中所说,我想使用不同的“随机化因子”从列表中获取一个随机元素。背景如下: 我有一个班级列表,其中我不知道班级的数量 此列表中的所有类都使用一个方法扩展了一个公共超类,该方法返回在列表中选择它们的概率百分比 我可能有一个想法,比如加上所有班级出现的机会百分比,如果超过100%,则除以每个百分比,使相对机会保持不变,但总百分比不超过100%。它可能不是很清楚,如果不是的话,我会再解释一下。假设列表中有3个对象,这些对象的“百分比”(你应该称之为“权重”,因为它不是百分比)为4、7和9

正如在标题中所说,我想使用不同的“随机化因子”从列表中获取一个随机元素。背景如下:

  • 我有一个班级列表,其中我不知道班级的数量
  • 此列表中的所有类都使用一个方法扩展了一个公共超类,该方法返回在列表中选择它们的概率百分比

我可能有一个想法,比如加上所有班级出现的机会百分比,如果超过100%,则除以每个百分比,使相对机会保持不变,但总百分比不超过100%。它可能不是很清楚,如果不是的话,我会再解释一下。

假设列表中有3个对象,这些对象的“百分比”(你应该称之为“权重”,因为它不是百分比)为4、7和9

所有权重之和:20

因此,第一个元素应该平均从20次中选出4次,第二个元素应该从20次中选出7次,以此类推

因此,生成一个介于0和20之间的整数。如果结果介于0和4之间,请拾取第一个元素。如果结果介于4和11之间,则选择第二个,如果结果介于11和20之间,则选择最后一个


剩下的只是实现细节。

假设列表中有3个对象,这些对象的“百分比”(您应该称之为“权重”,因为它不是百分比)为4、7和9

所有权重之和:20

因此,第一个元素应该平均从20次中选出4次,第二个元素应该从20次中选出7次,以此类推

因此,生成一个介于0和20之间的整数。如果结果介于0和4之间,请拾取第一个元素。如果结果介于4和11之间,则选择第二个,如果结果介于11和20之间,则选择最后一个


剩下的只是实现细节。

只需将数字相加,在
[0,1)
中创建一个随机值,乘以总和,然后在列表中迭代,从结果中减去数字,直到得到一个<0:

List<MyClass> elements = ...
double sum = elements.stream().mapToDouble(MyClass::getChance).sum();
double rand = Math.random() * sum;
MyClass choice = null;
for (MyClass e : elements) {
    choice = e;
    rand -= e.getChance();
    if (rand < 0) {
        break;
    }
}
列表元素=。。。
double sum=elements.stream().mapToDouble(MyClass::getChance).sum();
double rand=Math.random()*和;
MyClass choice=null;
对于(我的e类:元素){
选择=e;
rand-=e.getChance();
if(rand<0){
打破
}
}

只需将数字相加,在
[0,1)
中创建一个随机值,乘以总和,然后在列表中迭代,从结果中减去数字,直到得到一个数字<0:

List<MyClass> elements = ...
double sum = elements.stream().mapToDouble(MyClass::getChance).sum();
double rand = Math.random() * sum;
MyClass choice = null;
for (MyClass e : elements) {
    choice = e;
    rand -= e.getChance();
    if (rand < 0) {
        break;
    }
}
列表元素=。。。
double sum=elements.stream().mapToDouble(MyClass::getChance).sum();
double rand=Math.random()*和;
MyClass choice=null;
对于(我的e类:元素){
选择=e;
rand-=e.getChance();
if(rand<0){
打破
}
}

如果您只想进行一次(或几次)查找,那么这个方法很好

但是,如果您要经常这样做,那么该解决方案执行的顺序搜索是无效的

为了获得更有效的解决方案,您需要更直接的查找,因此您需要按累积机会组织数据。这可以通过使用二进制搜索的数组或按累积机会键入的键来完成

使用
NavigableMap
,例如,然后可以使用查找选定对象:

返回与严格大于给定键的最小键关联的键值映射,如果没有此类键,则返回
null

下面是示例代码:

public class MyObj {
    private final String name;
    private final int weight;
    public MyObj(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }
    public String getName() {
        return this.name;
    }
    public int getWeight() {
        return this.weight;
    }
    @Override
    public String toString() {
        return this.name;
    }

    public static void main(String[] args) {
        // Build list of objects
        List<MyObj> list = Arrays.asList(
                new MyObj("A", 2),
                new MyObj("B", 6),
                new MyObj("C", 12)
        );

        // Build map keyed by cumulative weight
        NavigableMap<Integer, MyObj> weighedMap = new TreeMap<>();
        int totalWeight = 0;
        for (MyObj obj : list) {
            totalWeight += obj.getWeight();
            weighedMap.put(totalWeight, obj);
        }
        System.out.println(weighedMap);

        // Pick 20 objects randomly according to weight
        Random rnd = new Random();
        for (int i = 0; i < 20; i++) {
            int pick = rnd.nextInt(totalWeight);
            MyObj obj = weighedMap.higherEntry(pick).getValue();
            System.out.printf("%2d: %s%n", pick, obj);
        }
    }
}

如果您只想进行一次(或几次)查找,那么这个方法很好

但是,如果您要经常这样做,那么该解决方案执行的顺序搜索是无效的

为了获得更有效的解决方案,您需要更直接的查找,因此您需要按累积机会组织数据。这可以通过使用二进制搜索的数组或按累积机会键入的键来完成

使用
NavigableMap
,例如,然后可以使用查找选定对象:

返回与严格大于给定键的最小键关联的键值映射,如果没有此类键,则返回
null

下面是示例代码:

public class MyObj {
    private final String name;
    private final int weight;
    public MyObj(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }
    public String getName() {
        return this.name;
    }
    public int getWeight() {
        return this.weight;
    }
    @Override
    public String toString() {
        return this.name;
    }

    public static void main(String[] args) {
        // Build list of objects
        List<MyObj> list = Arrays.asList(
                new MyObj("A", 2),
                new MyObj("B", 6),
                new MyObj("C", 12)
        );

        // Build map keyed by cumulative weight
        NavigableMap<Integer, MyObj> weighedMap = new TreeMap<>();
        int totalWeight = 0;
        for (MyObj obj : list) {
            totalWeight += obj.getWeight();
            weighedMap.put(totalWeight, obj);
        }
        System.out.println(weighedMap);

        // Pick 20 objects randomly according to weight
        Random rnd = new Random();
        for (int i = 0; i < 20; i++) {
            int pick = rnd.nextInt(totalWeight);
            MyObj obj = weighedMap.higherEntry(pick).getValue();
            System.out.printf("%2d: %s%n", pick, obj);
        }
    }
}

就是这样!我不知道我怎么会忘记想到那样的东西,谢谢。就是这样!我不知道我怎么会忘记想到那样的东西,谢谢。解释得很好。但是@JB Nizet的答案更适合我的问题,因为我只需要用总权重初始化我的类,定义一个“阈值”对每个类的权重定义一个“阈值”,然后随机化。解释得很好。但是@JB Nizet的答案更适合我的问题,因为我只需要用总权重初始化我的类,为每个类定义一个权重“阈值”,然后随机化。