偏向随机数发生器 - Java

时间:2017-01-11 00:07:11

标签: java random

我正在寻找一个随机数字生成器,它偏向于给出数字"最远的地方"来自一组已经选定的数字。例如,如果我的范围是[1,50]并且我传入了一组数字,例如(1,20,40),那么我希望生成器能够"更喜欢"产生数字远离1,20和40.因此,50或30之类的数字更可能被抽取而不是20或40.我怀疑这可能已经存在。有谁知道我可以用于Java的这种实现?

3 个答案:

答案 0 :(得分:1)

听起来你想要一个weighted random distribution,其中一些数字比其他数字更有可能。

您必须做出的决定是随机分布曲线的外观,即所选数字的避免程度。

当您选择新号码时,您还需要决定避免持续多长时间。例如。对于特定数量的选秀权,前一个数字是否同样被避免,或者随着时间的推移逐渐消失?

分布曲线示例

enter image description here
enter image description here
enter image description here

最后一个显示淡出,其中40前三次选择,1前两次选择,20是最后一次选择,其中数字1822(包括)下一次选择的概率为0%。

答案 1 :(得分:1)

这是一种可以手动完成的方法。基本上我们的想法是我们接受一些我们不想生成的数字,然后我们生成一个随机数,如果该数字在数字列表中,我们不希望我们再次尝试最大数量重试。

public static void main(String[] args) {

        int times = 25;
        int[] listOfNumbers = {1, 2, 3};
        int max = 5, min = 1;

        while(times-- > 0)
        {   
            System.out.print(GeneratePreferredNumbers(listOfNumbers, max, min) + " ");
        }


    }//main method

    public static Integer GeneratePreferredNumbers(int[] listOfNotPreffered, int max, int min)
    {
        Random rand = new Random();
        int randomNum;
        int retry = 1; //increasing this lessons the likely of our non-preferred numbers to show up
        HashSet<Integer> notPrefer = new HashSet<>();

        //add all the numbers we don't want to generate into a HashSet for easy lookup
        for(int index = 0; index < listOfNotPreffered.length; index++)
            notPrefer.add(listOfNotPreffered[index]);

        do {
            randomNum = rand.nextInt((max - min) + 1) + min;
            if(notPrefer.contains(randomNum))
            {
                retry--;
            }
            //we found a good value, let's return it
            else{
                retry = 0;
            }
        } while (retry > 0);

        return randomNum;
    }

输出:

重试= 0(只是随机)

2 2 4 4 4 2 1 2 2 4 3 3 4 5 4 4 1 5 3 2 1 2 3 3 1 

重试= 1

1 2 5 3 3 4 3 1 2 1 4 1 3 3 1 1 5 3 5 4 2 1 3 4 5 

重试= 2

3 3 2 4 4 2 2 1 4 5 5 5 4 2 1 4 5 1 4 5 1 4 4 2 2 

重试= 3

5 5 5 5 4 4 4 4 2 4 5 5 1 4 5 4 3 5 4 4 4 5 3 1 2 

重试= 4

5 4 5 4 4 4 5 5 4 4 5 1 5 2 5 5 5 2 4 5 5 2 4 4 4 

重试= 5

4 4 4 4 4 4 4 4 5 5 4 4 5 4 5 4 4 5 4 5 4 5 5 5 5 

注意:我们允许算法重试的次数越多,我们的输出就越有可能包含我们想要的数字。这使您可以控制希望显示非首选数字的可能性。这是有道理的,因为如果我们要将重试增加到无限,那么只有当生成的数字不包含在我们的列表非首选数字中时,这才会停止。

希望这有帮助!

答案 2 :(得分:0)

我最终做了一些不同的事情,发现以下代码很有用:

package util.math.random;

import math.MersenneTwisterFast;

public class SigmoidBiasedDistribution {
    private MersenneTwisterFast mtf;
    private int min;
    private int max;
    private double minsigmoidscalefactor;
    private double maxsidmoidscalefactor;
    private double maxoffsetfactor;

public SigmoidBiasedDistribution(int min, int max, double minsigmoidscalefactor, double maxsidmoidscalefactor, double maxoffsetfactor) {
    this.mtf = new MersenneTwisterFast();
    this.minsigmoidscalefactor = minsigmoidscalefactor;
    this.maxsidmoidscalefactor = maxsidmoidscalefactor;
    this.maxoffsetfactor = maxoffsetfactor;
    this.min = min;
    this.max = max;
}

public int[] getPoints(int points) {
    int[] pts = new int[points];
    double lowoffset = mtf.nextDouble(true, true) * maxoffsetfactor;
    double highoffset = mtf.nextDouble(true, true) * maxoffsetfactor;
    double randomsigmoidscalefactor = mtf.nextDouble(true, true) * (maxsidmoidscalefactor - minsigmoidscalefactor) + minsigmoidscalefactor;
    System.out.println(randomsigmoidscalefactor);
    double pointsoffset = ((1 - highoffset) - lowoffset) / (points - 1);
    for(int i = 0; i < points; i++) {
        double offset = (i * pointsoffset) + lowoffset;
        double scalefactor = getHalfSigmoid(offset, randomsigmoidscalefactor);
        pts[i] = (int) (scalefactor * (max - min)) + min;
    }
    return pts;
}

//sigmoid scale factor can range from -1 to 1 (-1 represents most aggressive initial slope, 1 represents least aggressive initial slope, 0 is linear)
//https://www.desmos.com/calculator/tswgrnoosy
public double getHalfSigmoid(double value, double sigmoidscalefactor) {
    return (value - value * sigmoidscalefactor) / (sigmoidscalefactor - 2 * Math.abs(value) * sigmoidscalefactor + 1);
}

public static void main(String[] args) {
    SigmoidBiasedDistribution sbd = new SigmoidBiasedDistribution(5, 80, 5, 0, 0.5, 0.2);
    int[] pts = sbd.getPoints(3);
    for(int i = 0; i < pts.length; i++) {
        System.out.println(pts[i]);
    }
}   
}

基本上,代码只适合N个等距点到半S形曲线。或者,我也写了下面的解决方案,但我更喜欢上面的解决方案。

package util.math.random;

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

import math.MersenneTwisterFast;

public class WeightedRandomGenerator {
    private double sigmoidscalefactor;
    private MersenneTwisterFast mtf;
    private List<Integer> points;
    private int min;
    private int max;
    private int numcompletelyrandom;

public WeightedRandomGenerator(int min, int max, int numcompletelyrandom, double sigmoidscalefactor) {
    this.mtf = new MersenneTwisterFast();
    this.points = new ArrayList<Integer>();
    this.numcompletelyrandom = numcompletelyrandom;
    this.sigmoidscalefactor = sigmoidscalefactor;
    this.min = min;
    this.max = max;
    clear();
}

public int nextInt() {
    if(points.size() - 2 < numcompletelyrandom) {
        int nextint = mtf.nextInt(max - min + 1) + min;
        points.add(nextint);
        return nextint;
    }
    else {
        int maxsep = getMaxSeparation();
        while(true) {
            int nextint = mtf.nextInt(max - min + 1) + min;
            int nearestneighbor = getNearestNeighbor(nextint);
            double sepfrac = (double) nearestneighbor / (double) maxsep;
            if(mtf.nextBoolean(getHalfSigmoid(sepfrac))) {
                points.add(nextint);
                return nextint;
            }
        }
    }
}

private int getNearestNeighbor(int nextint) {
    int delta = Integer.MAX_VALUE;
    for(int i = 0; i < points.size(); i++) {
        delta = Math.min(delta, Math.abs(nextint - points.get(i)));
    }
    return delta;
}

private int getMaxSeparation() {
    int maxsep = 0;
    for(int i = 1; i < points.size(); i++) {
        int delta = points.get(i) - points.get(i-1);
        maxsep = Math.max(delta, maxsep);
    }
    return maxsep;
}

private void clear() {
    points.clear();
    points.add(min);
    points.add(max);
}

//sigmoid scale factor can range from -1 to 1 (-1 represents most aggressive initial slope, 1 represents least aggressive initial slope, 0 is linear)
//https://www.desmos.com/calculator/tswgrnoosy
public double getHalfSigmoid(double value) {
    return (value - value * sigmoidscalefactor) / (sigmoidscalefactor - 2 * Math.abs(value) * sigmoidscalefactor + 1);
}

public static void main(String[] args) {
    WeightedRandomGenerator wrg = new WeightedRandomGenerator(1, 80, 2, 0.95);
    for(int i = 0; i < 5; i++) {
        System.out.println(wrg.nextInt());
    }
}
}