Nathan Hornby
Nathan Hornby

Reputation: 1443

Add weight to randomisation

I'm struggling to find any resources that help with this question:

Say I want to generate a random number between 1 and 5, but wish the randomisation to be weighted toward the bottom end (like if generating a random number of children a couple might have, which is more likely to be 1 than 5). Where should I start? Would this involve creating a complex function to map that curve or is there a method available to help with this?

For very simple sets I suppose I could create an array, so for the above example I could do, for example:

var children = [1,1,1,1,2,2,2,3,4,5];
return children[Math.floor(Math.random() * children.length)];

But this lacks flexibility and assumes the data set is being created manually within the script.

Upvotes: 3

Views: 182

Answers (4)

Ivan Kuckir
Ivan Kuckir

Reputation: 2549

The "children birth probability" has Gaussian distribution, which is a "continuous limit" of binomial distribution. What about using binomial distribution?

var val = Math.random()*5 + Math.random()*5;
// "val" will be between 0 and 10, but values around 5 are more probable
return 1 + Math.floor(Math.abs(val-5));

Just put it into function and make "5" a parameter.

Small test (http://jsfiddle.net/yt6zvs8n/):

100 output values: 1, 1, 2, 3, 2, 4, 2, 2, 2, 3, 3, 1, 2, 1, 4, 1, 2, 3, 2, 1, 4, 4, 2, 1, 4, 3, 2, 2, 1, 3, 2, 4, 1, 3, 2, 4, 2, 2, 1, 2, 1, 1, 1, 4, 1, 4, 1, 3, 1, 2, 1, 2, 1, 2, 2, 3, 2, 1, 3, 1, 2, 1, 3, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 3, 4, 1, 3, 1, 2, 5, 1, 3, 1, 3, 1, 3, 1, 4, 1, 3, 1, 3, 2, 2, 3, 1, 3, 4, 1, 3

Occurences (histogram): 38, 28, 21, 12, 1

Upvotes: 5

bto.rdz
bto.rdz

Reputation: 6720

since the number Math.random() is between 0 and 1, all you have to do is create an array with the probabity that the answer is 1,2,3,4 or 5. for example

var px = [
    { answer: 1, pxi: 0.0, pxf:0.40  },
    { answer: 2, pxi: 0.40, pxf:0.60  },
    { answer: 3, pxi: 0.60, pxf:0.80  },
    { answer: 4, pxi: 0.80, pxf:0.90  },
    { answer: 5, pxi: 0.90, pxf:1  }
]

lets say r is a random number, so if r > pxi && r<=pxf it will return an x answer.

so if i am clear enough you can see that the propability that this method returs 1 is 40%, 2 is 20%, 3 is 20%, 4 is 10% and 5 10%, you can easly change the probability of the anwer by changing the range pxi-pxf

Upvotes: 0

axelduch
axelduch

Reputation: 10849

Well as long as you work with integers, where N is the max child value and N is not so big you will need O(n) space and time

I'll answer assuming that you only use integers going from 0 to N, see in USAGE section of my answer why you don't need to specify them by hand when their weight is 0.

var weights = [0,4,3,1,1,1];

var number = weights.reduce(function (previous, current, index) {
    var value = Math.random() * (current|0);
    if (value > previous[0]) {
        return [value, index];
    }
    return previous;
}, [Math.random() * (weights[0]|0), 0])[0];

Explanation

Using indices as integers, we have:

  • 0 with a weight of 0 (it will never appear)
  • 1 with a weight of 4
  • 2 with a weight of 3
  • 3 with a weight of 1
  • 4 with a weight of 1
  • 5 with a weight of 1

Usage

As it is not very easy to think in term of range or if you don't want to specify all values until N manually, you can use an object and convert it to an array by specifying a key length equal to N + 1, like this

var N = 130, 
    weights = Array.apply(null, {
        12: 1,
        53: 3,
        130: 10,
        length: N + 1
    });

Upvotes: 3

Jon
Jon

Reputation: 437386

You can use the idea of assigning arbitrary weights to each possible outcome without having to create an array as large as that. It's enough to assign an arbitrary weight to each possible choice, then convert a random number to the appropriate result. The possible results of the random roll need not even be a consecutive integer range.

For example:

var weights = { 1: 4, 2: 3, 3: 1, 4: 1, 5: 1 }

// pre-processing; modifies weights!
var runningTotal = 0;
for (var number in weights) {
    runningTotal += weights[number];
    weights[number] = runningTotal;
}

// generation
var seed = Math.floor(Math.random() * runningTotal);

// conversion of seed to result based on weights
for (var number in weights) {
    if (seed < weights[number]) {
        console.log("result: " + number);
        break;
    }
}

Note that the above is meant as an illustrative example, not as the best way to write this code.

See it in action.

It's possible to generalize this even further so that you don't even have to provide input that explicitly specifies every possible result, but the same principle will still apply.

Upvotes: 1

Related Questions