【问题标题】:Randomly getting value with percentage of chance in Java在 Java 中以机会百分比随机获取价值
【发布时间】:2021-02-18 14:11:15
【问题描述】:

我希望能够根据每个项目的某个预定义概率百分比返回(在我们的例子中,例如显示)枚举中的一个项目。此枚举的代码说明如下:

package me.lucas.test;

public enum Item {

    ITEM_1("Item 1", 30),
    ITEM_2("Item 2", 30),
    ITEM_3("Item 3", 10),
    ITEM_4("Item 4", 65);

    private final String name;
    private final int percentage;

    Item(String name, int percentage) {
        this.name = name;
        this.percentage = percentage;
    }

    public String getName() {
        return name;
    }

    public int getPercentage() {
        return percentage;
    }

}

此外,这些项目将存储在列表中。

package me.lucas.test;

import java.util.ArrayList;
import java.util.List;

public class Program {

    private final List<Item> items = new ArrayList<>();

    /**
     * Displays an item among those in the list, according to their percentage chance of appearing.
     * @param items The items
     */
    public void displayItem(List<Item> items) {
        Item item;

        item = ?

        System.out.println("Selected item : " + item.getName() + " with percentage : " + item.getPercentage());
    }

}

也就是说,如本例所示,两个项目出现的机会百分比相同。

我还查看了以下主题 (Java Random Percentage Chance),但就我而言,百分比不是直接在表达式中而是在枚举中预定义的。

因此,我将感谢您的帮助。提前感谢您抽出宝贵时间帮助我。

【问题讨论】:

  • 在运行这个例子时,你想显示或不显示一些东西,基于机会,相同的机会,对于列表中的所有项目,执行?是这样吗?
  • 请注意,您的总百分比大于 1(即 135%)
  • 这不是家庭作业写作服务。 135% 就像 100% 一样容易计算出来。
  • @lucasparmentier388:好吧,如果你有 2 件物品的几率为 100%,会发生什么?会选择哪一个?那你的预期结果是什么?这就是为什么我要指出非 100% 的总数
  • 如果概率总和小于 100%,则一定意味着有时什么都没有返回。同样,如果概率之和大于 100%,则一定意味着有时会返回多个值。简而言之,除非概率总和正好是 100%,否则返回值必须是 list 值。 --- 或者,您可以将值重新分类为 weight,而不是 percentage,在这种情况下,它们不必添加任何特定的值,它们都必须大于零。

标签: java random percentage enumeration


【解决方案1】:

简单!!!

  1. 创建一个数组。
  2. 将所有项目放入此数组,percentage 计数。
  3. 随机排列数组。
  4. 从此数组中检索随机元素。

public class Foo {

    public static void main(String... args) {
        Item[] items = generateItemArray();
        Random random = new Random();

        while (true) {
            System.out.println(items[random.nextInt(items.length)]);
        }
    }

    private static Item[] generateItemArray() {
        List<Item> items = new ArrayList<>();

        for (Item item : Item.values())
            for (int i = 0; i < item.percentage; i++)
                items.add(item);

        Collections.shuffle(items);
        return items.toArray(Item[]::new);
    }

    public enum Item {
        ITEM_1("Item 1", 30),
        ITEM_2("Item 2", 30),
        ITEM_3("Item 3", 10),
        ITEM_4("Item 4", 65);

        private final String name;
        private final int percentage;

        Item(String name, int percentage) {
            this.name = name;
            this.percentage = percentage;
        }

    }
}

【讨论】:

  • 显然比我的轮子实现简单。但是以我认为错误的方式处理总和不是% 100 == 0 的百分比:如果总和小于 100,则不可能不返回任何值。如果总和 > 100,则没有机会返回多个值。为了解决这个问题,我建议用null 值填充items 直到items.size() % 100 == 0 并在每次抽奖时返回items.length / 100 项目。否则百分比实际上是重量。
  • 其实想一想:上面的改动还是没有解决100%的问题。有可能有一个项目应该有 100% 的机会被挑选,但仍然永远不会被退回。
【解决方案2】:

从视觉上讲,您实现了机会之轮

轮子将落在 1 到 100 的任何位置。现在您只需读出该位置有哪些可能性。如果总和小于 100,则此位置可能没有任何内容。如果总和 > 100,则该位置可能不止一项。

所有百分比都在 1 到 100 之间换行 - 通过取模来实现。您生成的随机数介于 1 和 100 之间,或者在本例中介于 0 和 99 之间(请参阅 Javadoc of Random)。

在代码中可能如下所示:

public class RandomExample<T extends Object> {
    List<Item> items = new LinkedList<>();
    Random rand = new Random();
    
    public void addItem(int percentage, T object) {
        items.add(new Item(percentage, object));
    }
    
    public List<Item> getRandom() {
        List<Item> result = new ArrayList<>();
        int hit = rand.nextInt(100);
        items.forEach(i -> {
            if(i.isHit(hit)) {
                result.add(i);
            }
        });
        return result;
    }
    
    public class Item {
        private int percentage;
        private T object;
        private int hitLow;
        private int hitHigh;
        
        public Item(int percentage, T object) {
            this.percentage = percentage;
            this.object = object;
            int sum = 0;
            for(Item i : items) {
                sum+=i.getPercentage();
            }
            hitLow = sum % 100;
            hitHigh = (sum+percentage) % 100;
        }
        
        public int getPercentage() {
            return percentage;
        }
        
        public T getObject() {
            return object;
        }
        
        public boolean isHit(int hitVal) {
            boolean isHit = hitLow == hitHigh; //exactly 100%
            isHit |= hitLow <= hitVal && hitHigh >= hitVal;
            return isHit;
        }
    }

}

方法RandomExample.Item.isHit(int) 仍然需要一个额外的条件来正确处理机会,它环绕在100 左右(例如hitLow = 90hitHigh = 10)。但是您了解了这个概念,并且应该能够根据您的需要对其进行调整。

您需要问自己的另一个问题是:如果有人通过了大于 100 的百分比会发生什么。我的直觉是您应该抛出异常。但这是由您决定和实施的。

【讨论】:

  • 这意味着您只能将红色和黄色放在一起,而不能将绿色和红色或绿色和黄色放在一起?如果选择了每个“颜色”(枚举值),您是否不必检查它?我同意这个问题并不清楚确切的行为。
  • 是的,如果总数超过 100%,则只有某些组合是可能的。如果您想要所有可能的组合,则轮子不起作用。然后我认为您需要为每个 Item 生成一个随机数以确定它是否被绘制。之后,需要进行一些算术判断来检查是否满足百分比的约束 - 两个 90% 的项目仍可能导致不抽签,但由于总数为 180%,因此至少需要抽签一项。 (至少我是这么理解这个问题的)
  • 您好,这几天有点忙,很抱歉。我正在考虑不再使用枚举,因为我将基于配置文件创建对象。我不太了解 isHit 函数。如果我假设总和为 100,是否会使事情变得更容易?
  • 您好,变量hitLowhitHigh只标记了轮子的上下边界,这里会选择这个选项。方法isHit 中的参数hitVal 是车轮实际停止的值。因此,该方法检查所选值是否在这些边界之间。是否在旋转轮子时选择了该项目。假设每个项目的百分比 isHit |= hitLow > hitHigh && hitLow >= hitVal && hitHigh <= hitVal;。如果您有百分比 > 100% 的项目,则需要更改此行为。
  • 为了可重用性,我仍然建议在对象周围使用某种包装器以供选择。因此,您的代码可用于枚举、字符串或您决定从配置文件生成的任何对象。对于您的另一个问题:如果您确保所有百分比的总和始终为 100 - @oleg.cherednik 的方法更容易且性能更高。为了确保不会出现一些意外行为,您应该始终检查总和是否为 100。尤其是在使用配置文件时(可能配置错误),否则可能会导致难以追踪的问题。
猜你喜欢
  • 2021-01-19
  • 1970-01-01
  • 2021-12-20
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 2018-08-20
  • 2018-09-16
  • 1970-01-01
相关资源
最近更新 更多