java - 随机百分比分支的编码模式?

标签 java design-patterns random

假设我们有一个代码块,我们希望执行 70% 的时间和另一个 30% 的时间。

if(Math.random() < 0.7)
    70percentmethod();
else
    30percentmethod();

足够简单。但是,如果我们希望它可以轻松扩展为 30%/60%/10% 等呢? 在这里,它需要添加和更改所有关于 change 的 if 语句,这不太好用,速度慢而且会导致错误。

到目前为止,我发现大型开关对于这个用例非常有用,例如:

switch(rand(0, 10)){
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:70percentmethod();break;
    case 8:
    case 9:
    case 10:30percentmethod();break;
}

很容易改成:

switch(rand(0, 10)){
    case 0:10percentmethod();break;
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:60percentmethod();break;
    case 8:
    case 9:
    case 10:30percentmethod();break;
}

但这些也有其缺点,即繁琐且分成预定数量的分区。

理想的东西是基于我猜的“频率数”系统,如下所示:

(1,a),(1,b),(2,c) -> 25% a, 25% b, 50% c

如果你添加了另一个:

(1,a),(1,b),(2,c),(6,d) -> 10% a, 10% b, 20% c, 60% d

所以只需将数字相加,使总和等于 100%,然后将其拆分。

我想用自定义的 hashmap 或其他东西为它创建一个处理程序不会有那么麻烦,但我想知道在我把所有的意大利面条放在这之前是否有一些既定的方式/模式或 lambda。

最佳答案

编辑:请参阅最后的编辑以获得更优雅的解决方案。不过我会把它留在里面。

您可以使用 NavigableMap 来存储这些方法映射到它们的百分比。

NavigableMap<Double, Runnable> runnables = new TreeMap<>();

runnables.put(0.3, this::30PercentMethod);
runnables.put(1.0, this::70PercentMethod);

public static void runRandomly(Map<Double, Runnable> runnables) {
    double percentage = Math.random();
    for (Map.Entry<Double, Runnable> entry : runnables){
        if (entry.getKey() < percentage) {
            entry.getValue().run();
            return; // make sure you only call one method
        }
    }
    throw new RuntimeException("map not filled properly for " + percentage);
}

// or, because I'm still practicing streams by using them for everything
public static void runRandomly(Map<Double, Runnable> runnables) {
    double percentage = Math.random();
    runnables.entrySet().stream()
        .filter(e -> e.getKey() < percentage)
        .findFirst().orElseThrow(() -> 
                new RuntimeException("map not filled properly for " + percentage))
        .run();
}

NavigableMap 是按键排序(例如 HashMap 不保证条目),因此您可以得到按百分比排序的条目.这是相关的,因为如果您有两个项目 (3,r1),(7,r2),它们会产生以下条目:r1 = 0.3r2 = 1.0 并且需要按此顺序评估它们(例如,如果以相反的顺序评估它们,则结果将 alwaysr2)。

至于拆分,应该是这样的: 像这样的元组类

static class Pair<X, Y>
{
    public Pair(X f, Y s)
    {
        first = f;
        second = s;
    }

    public final X first;
    public final Y second;
}

您可以像这样创建 map

// the parameter contains the (1,m1), (1,m2), (3,m3) pairs
private static Map<Double,Runnable> splitToPercentageMap(Collection<Pair<Integer,Runnable>> runnables)
{

    // this adds all Runnables to lists of same int value,
    // overall those lists are sorted by that int (so least probable first)
    double total = 0;
    Map<Integer,List<Runnable>> byNumber = new TreeMap<>();
    for (Pair<Integer,Runnable> e : runnables)
    {
        total += e.first;
        List<Runnable> list = byNumber.getOrDefault(e.first, new ArrayList<>());
        list.add(e.second);
        byNumber.put(e.first, list);
    }

    Map<Double,Runnable> targetList = new TreeMap<>();
    double current = 0;
    for (Map.Entry<Integer,List<Runnable>> e : byNumber.entrySet())
    {
        for (Runnable r : e.getValue())
        {
            double percentage = (double) e.getKey() / total;
            current += percentage;
            targetList.put(current, r);
        }
    }

    return targetList;
}

所有这些都添加到一个类中

class RandomRunner {
    private List<Integer, Runnable> runnables = new ArrayList<>();
    public void add(int value, Runnable toRun) {
        runnables.add(new Pair<>(value, toRun));
    }
    public void remove(Runnable toRemove) {
        for (Iterator<Pair<Integer, Runnable>> r = runnables.iterator();
            r.hasNext(); ) {
            if (toRemove == r.next().second) {
               r.remove();
               break;
            }
        }
    }
    public void runRandomly() {
        // split list, use code from above
    }
}

编辑:
实际上,如果您有一个想法卡在脑海中并且没有正确质疑它,那么您就会得到上述结果。 保留 RandomRunner 类接口(interface),这样就容易多了:

class RandomRunner {
    List<Runnable> runnables = new ArrayList<>();
    public void add(int value, Runnable toRun) {
        // add the methods as often as their weight indicates.
        // this should be fine for smaller numbers;
        // if you get lists with millions of entries, optimize
        for (int i = 0; i < value; i++) {
            runnables.add(toRun);
        }
    }
    public void remove(Runnable r) {
        Iterator<Runnable> myRunnables = runnables.iterator();
        while (myRunnables.hasNext()) {
            if (myRunnables.next() == r) {
                myRunnables.remove();
            }
    }
    public void runRandomly() {
        if (runnables.isEmpty()) return;
        // roll n-sided die
        int runIndex = ThreadLocalRandom.current().nextInt(0, runnables.size());
        runnables.get(runIndex).run();
    }
}

关于java - 随机百分比分支的编码模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45836397/

相关文章:

r - 在 R 中,如何在节点之间随机生成边?

java - 带有区域设置的日期格式

java - 将对象的数组列表返回给 servlet 的 ajax 调用

java - 将属性传递给工厂方法

java - 应用程序属性作为静态变量或带有单例的实例变量

java - 随机生成边和顶点

java - 使用 Java 从可运行的 jar 中更改 Windows 7 桌面背景

java - 在服务器上发布本地生成的报告

C# 克隆图和更新循环引用

c# - C# MVC4.0 中的有效 Math.Random