JavaScript weighted random or percent of the time

Problem

I have a Node.js process (setInterval) that runs every 100ms. I have certain actions that I want to take every x period of time. So for example, 2% of the time do X, 10% of the time do Y, etc.

Right now, I'm basically doing it like this:

var rand = Math.floor(Math.random() * (1000 + 1));

if(rand > 900) {  // Do something }

if(rand > 950) {  // Do something }

The problem is it's very inconsistent. You would want if(rand > 900) to be at least close to 10% of the time, but sometimes it may be 10x in a row or not at all.

Would anyone have suggestions to a better solution that would be more accurate if we assume the 100ms interval is fixed.

Thank you!

Edit: Based on Dr. Dredel's comments:

var count = 0;
setInterval(function(){

    if(count++ % 4 == 0) {
       console.log('25% of the time');
    }

}, 100);‚Äč
Problem courtesy of: dzm

Solution

If your interval is fixed, I would round your stamp to the nearest hundred and then use those segments that relate to your needs... 100 and 200 but not 300- 1000 to represent 2%.

If you CAN use a counter, then that's the more obvious way to do it.

if(myCounter++ % 4 == 0)
    //this happens 25 percent of the time 

As Emil points out, probability is not the correct approach here, and I don't get the sense that you're married to it... It sounds like you're using it because you didn't see a better way to provoke something to happen x% of the time. If we're misunderstanding you, you need to explain in better detail why you're using odds here.

Solution courtesy of: Genia S.

Discussion

If you want to guarantee that your operations happen an exact percentage of the time (rather than leave that to chance), but you want them to be selected in a random order, then you can do something like this where you create a data structure with the exact outcomes you want in it for one iteration through all elements. Then, you randomly pick one of those outcomes, remove it from the data structure, randomly pick another outcome and so on...

If you seed the initial data structure with the proper percentage of each outcome, then you will get outcomes according to that rule and for each full iteration through, you will get exactly the desired number of each outcome, but they will be selected in random order and that order will be different each time.

If you want the process to repeat over and over, you can just start it over each time it finishes one complete iteration.

var playProbabilities = [
    {item: "A", chances: 3},
    {item: "B", chances: 2},
    {item: "C", chances: 1},
    {item: "D", chances: 2},
    {item: "E", chances: 1},
    {item: "F", chances: 1}
];

function startPlay(items) {
    var itemsRemaining = [];
    // cycle through the items list and populate itemsRemaining
    for (var i = 0; i < items.length; i++) {
        var obj = items[i];
        // for each item, start with the right number of chances
        for (var j = 0; j < obj.chances; j++) {
            itemsRemaining.push(obj.item);
        }
    }
    return(itemsRemaining);
}

function nextPlay(itemsRemaining) {
    if (!itemsRemaining.length) {
        return null;
    }
    // randomly pick one
    var rand = Math.floor(Math.random() * itemsRemaining.length);
    var result = itemsRemaining[rand];

    // remove the one we picked from the array
    itemsRemaining.splice(rand, 1);
    return(result);
}

$("#go").click(function() {
    var results = $("#results");
    var items = startPlay(playProbabilities);
    var next;
    while(next = nextPlay(items)) {
        results.append(next + "<br>");
    }
    results.append("-------------<br>");
});

Working demo here: http://jsfiddle.net/jfriend00/x2v63/

If you run the demo, you will see that for each run, it produces exactly the desired number of each outcome, but they are selected in random order.

Discussion courtesy of: jfriend00

Introduce a counter and BAM! Now you can have it exactly 2% of the time!

Seriously, introducing some sort of state is the only way for you to enforce a "not too many times in a row" policy. Probability/randomness cannot help you with this problem. The belief that random events can't happen many times in a row is a well-known myth. In fact, a 2% probable event can happen millions of times in a row, although it's very unlikely.

You will need to add a constraint such as "I want the event to happen with x% probability, but I always want it to go at least y steps after each event".

Discussion courtesy of: Emil Vikström

This recipe can be found in it's original form on Stack Over Flow.