2

I wanted to write a simple function that allows to roll random item from list, i did it with this code:

this.resources = [false, 'nitrogen', 'silicon', 'cobalt', 'magnesium'];

this.assign_resource = function() {
    var index = tools.rnd(0, this.resources.length - 1);
    return this.resources[index];
};

But it doesn't play well, so i wanted to change it to different system that allows a list of items (including empty one) and it picks one at random but each one has different chance (for example this one has 10%, this one has 20%). Maybe someone could help me with this kind of function.

Edited -----

for example this could be new list:

this.resources = [
    { type: 'empty', chance: 30 },
    { type: 'nitrogen', chance: 10 },
    { type: 'silicon', chance: 20 },
    { type: 'cobalt', chance: 30 },
    { type: 'magnesium', chance: 10 }
];

How to use it now to make it happen properly?

Edited 2 -----

I am trying to figure out well done programming solution using math rather then simply duplicating items in array, answers presented in this topic are just work arounds to a problem.

  • 1
    I don't know how well you could do it with inputting percentages but you can just make an array with the same value more than once, for example `[1,1,2]` would give `1` a 2/3 probability or being returned based on a random index number. – George Jun 08 '17 at 14:04
  • 1
    Where do you specify the chance? Like this? `this.resources = [false, ['nitrogen', 0.5], ['silicon', 0.2], ['cobalt', 0.2], ['magnesium', 0.1]];` Also, is `false` for cases where no resource is assigned? – Majid Fouladpour Jun 08 '17 at 14:05
  • 1
    Adding to my comment, you'd want something like [this](https://stackoverflow.com/questions/8877249/generate-random-integers-with-probabilities) – George Jun 08 '17 at 14:08
  • I updated my question with possible chances for particular item to happen –  Jun 08 '17 at 14:09
  • Possible duplicate of [generate random integers with probabilities](https://stackoverflow.com/questions/8877249/generate-random-integers-with-probabilities) – Yury Tarabanko Jun 08 '17 at 14:11
  • I have seen that topic its not what i am looking for, i am rather after well done programming solution rather then duplicating values in array, other examples are faulty even –  Jun 08 '17 at 14:13
  • https://stackoverflow.com/a/28933315/351705 this approach should work w/o duplicating values. – Yury Tarabanko Jun 08 '17 at 14:18
  • The OP says: *"i am rather after well done programming solution rather then duplicating values in array"*. And the accepted answer delivers exactly that: it does not store the product of `item x chance` anywhere. Sure the answer uses a clever approach to satisfy the condition, but *the condition* in my view is just a personal sentiment and is hurting the implementation by making it less performant. – Majid Fouladpour Jun 08 '17 at 15:23

5 Answers5

4

I'd solve it by having an array of objects with a chance to be the result, totalling 1.0, then picking a random number between 0 and 1, and then iterating over the resources and check if adding it to a cumulative total includes your random number.

var resources = [
  { resource: false, chance: 0.2 },
  { resource: 'nitrogen', chance: 0.1 },
  { resource: 'silicon', chance: 0.2 },
  { resource: 'cobalt', chance: 0.45 },
  { resource: 'mangesium', chance: 0.05 }    
];

function get_result(resouceList) {
  //get our random from 0 to 1
  var rnd = Math.random();
  
  //initialise our cumulative percentage
  var cumulativeChance = 0;
  //iterate over our resources
  for (var i = 0; i < resouceList.length; i++) {
    
    //include current resource
    cumulativeChance += resouceList[i].chance;
    
    if (rnd < cumulativeChance)
      return resouceList[i].resource;
  }
  
  return false;  
}

//test
console.log(get_result(resources));
console.log(get_result(resources));
console.log(get_result(resources));
console.log(get_result(resources));
console.log(get_result(resources));
Dan F
  • 85
  • 6
1

You can do something like this.

Creating an array with the same value multiple times gives it a higher chance of being selected.

var resources = [{
    type: 'empty',
    chance: 30
  },
  {
    type: 'nitrogen',
    chance: 10
  },
  {
    type: 'silicon',
    chance: 20
  },
  {
    type: 'cobalt',
    chance: 30
  },
  {
    type: 'magnesium',
    chance: 10
  }
];


function GetRandom(list) {
  var array = [];
  for (var i = 0; i < list.length; i++) {
    var item = list[i];
    var chance = item.chance / 10;
    for (var j = 0; j < chance; j++) {
      array.push(item.type);
    }
  }
  var idx = Math.floor(Math.random() * array.length);
  return array[idx];

}

console.log(GetRandom(resources))
.as-console-wrapper { max-height: 100% !important; top: 0; }
George
  • 6,630
  • 2
  • 29
  • 36
1

I would set it up so that only the actual resources are in your array and "empty" happens if the random roll falls outside of those.

this.resources = [
    { type: 'nitrogen', chance: 10 },
    { type: 'silicon', chance: 20 },
    { type: 'cobalt', chance: 30 },
    { type: 'magnesium', chance: 10 }
];

this.assign_resource = function() {
  var rnd = Math.random();
  var acc = 0;
  for (var i=0, r; r = this.resources[i]; i++) {
    acc += r.chance / 100;
    if (rnd < acc) return r.type;
  }
  // rnd wasn't less than acc, so no resource was found
  return 'empty';
}
James
  • 20,957
  • 5
  • 26
  • 41
  • I would order the array by chance first. Because logically, if you set chance to 100, you'd expect that element to return, unless there's another one set to 100. Maybe that's why it needs to be calculated not over 100, but over sum of the chances. – s.alem Jun 08 '17 at 15:16
  • @s.alem Yeah that's a valid alternative, if you're not using percent chance. I like this technique because it doesn't mean you have to maintain an "empty" element, also it means that if you add a new item to the table you don't have to adjust all the other chances manually, as long as everything is a reasonable percent and the total doesn't exceed 100. – James Jun 08 '17 at 15:32
1

This is how I'd implement the solution. Step 1: accumulate all the possible chances Step 2: pick a random value in proportion to total chance Step 3: loop through the resources to see in which part it random value falls under.

var resources = [
    { type: 'empty', chance: 30 },
    { type: 'nitrogen', chance: 10 },
    { type: 'silicon', chance: 20 },
    { type: 'cobalt', chance: 30 },
    { type: 'magnesium', chance: 10 }
];

function solution(resources) {
  let chanceTotal = resources.reduce((acc, val) => { acc += val.chance ; return acc;}, 0);
  let randomVal = parseInt(Math.random() * chanceTotal);
  let chanceAcc = 0;
  let ans;
  resources.forEach(r => {
    chanceAcc += r.chance;
    if (chanceAcc > randomVal && !ans) {
      ans = r;
    }
  });
  return ans;
}

console.log(solution(resources));
Mμ.
  • 8,382
  • 3
  • 26
  • 36
1

Here is another implementation.

var res = [
    ["empty", 3],
    ["nitrogen", 1],
    ["silicon", 2],
    ["cobalt", 3],
    ["magnesium", 1]
];
var flat = [];
var item;
for(var i = 0, l = res.length; i < l; i++) {
    item = Array(res[i][1]+1).join(i+",");
    item = item.substr(0, item.length-1);
    flat.push(item);
}
flat = flat.join(",").split(",");

function get_random_item() {
    var ridx = Math.floor(Math.random() * (flat.length));
    return res[flat[ridx]][0];
}

var pick;
for(var p = 0; p < 50; p++) {
    pick = get_random_item();
    console.log(p, pick);
}
Majid Fouladpour
  • 29,356
  • 21
  • 76
  • 127
  • @Shafizadeh, I hope the answer had enough merit of its own for the upvote ;) (I know, just joking). You'll find my contact info on my profile. – Majid Fouladpour Jul 13 '17 at 21:56