Page 1 of 1

Random with probability

Posted: Fri Nov 03, 2017 11:51 am
by darkfrei
Hi all!
How to make this function?

I have a

Code: Select all

table = {
{name = "name-01", probability = 100},
{name = "name-02", probability = 90},
{name = "name-03", probability = 20},
{name = "name-04", probability = 20},
...
}
And I want to get randomly element from this table.
The first step is easy, I need to get summary of all probabilities and then I can get

Code: Select all

number =math.rnd(probability_summ(table))
How to get the element from this table?

Re: Random with probability

Posted: Fri Nov 03, 2017 12:15 pm
by ratchetfreak
create the cumulative sum and then pick a number between 0 and the sum of the original:

Code: Select all

table = {
{name = "name-01", probability = 100, cumsum = 100},
{name = "name-02", probability = 90, cumsum = 190},
{name = "name-03", probability = 20, cumsum = 210},
{name = "name-04", probability = 20, , cumsum = 230},
...
}

number =math.rnd(probability_summ(table));

for i,v in ipairs(a) do 
    if(number < v.cumsum) then result = i; break; end
end
// or binary search instead.

Re: Random with probability

Posted: Fri Nov 03, 2017 2:09 pm
by eradicator
Propability is usually expressed as a value between 0 and 100%. Your tables total probability is >100. So you should recalculate the values to (42,40,9,9) to make it easier to tell how probable each entry occurs. (Unless your intent is to have the possibility for several "entries" to occur at the same time in which case the above solution is wrong). If you want to keep the numeric values as in your example then they're not probability values but weight values and the variable should be renamed.

This...is ofc all assuming you care about making your code readable to other people :P

Re: Random with probability

Posted: Fri Nov 03, 2017 2:46 pm
by darkfrei
eradicator wrote:Propability is usually expressed as a value between 0 and 100%. Your tables total probability is >100.
Probability can be written by "permille", then total must be 1000.

But you are right, it was all about weights. In this case weights are better, I can use integer only, the summary will be always 100%, not 'fast 100' or 'a bit more then 100' caused by fraction rounding.

Re: Random with probability

Posted: Fri Nov 03, 2017 2:56 pm
by Klonan
We do something similar for team production script:
The definition

Code: Select all

  global.challange_type_probabilities =
  {
    {probability = 14, value = "production"},
    {probability = 2, value = "research"},
    {probability = 14, value = "shopping_list"},
    {probability = 8, value = "input_output"},
  }
And how to choose

Code: Select all

function select_from_probability_table(probability_table)
  local roll_max = 0
  for _, item in pairs(probability_table) do
    roll_max = roll_max + item.probability
  end

  local roll_value = math.random(0, roll_max - 1)
  for _, item in pairs(probability_table) do
    roll_value = roll_value - item.probability
    if (roll_value < 0) then
      return item.value
    end
  end
end

Re: Random with probability

Posted: Fri Nov 03, 2017 4:38 pm
by eradicator
darkfrei wrote:
eradicator wrote:Propability is usually expressed as a value between 0 and 100%. Your tables total probability is >100.
Probability can be written by "permille", then total must be 1000.
Just making sure you actually wanted weight and not per-item-probability. Because (like Klonans example too) if i read "probability=14" i first assume that "that thing" has 14% chance to happen, and not 36% :P.
If you need to do this really fast you might try pre-caching the result:

Code: Select all

local guys = {
  {who='beggar' , weight=20},
  {who='burglar', weight=50},
  {who='bandit' , weight=10},
  {who='barman' , weight=10},
  }

Code: Select all

local brawl = {}
for k,guy in iparis(guys) do
  for i=1,guy.weight do
     brawl[#brawl+1] = k
    end
  end

local injuries = #brawl

local function fight()
  return guys[brawl[math.random(injuries)]]
  end

Code: Select all

local winner = fight()
So every roll requires just two table lookups. Can be reduced to one lookup if you put the 'guy's name directly into the brawl table but then it costs more memory.

Re: Random with probability

Posted: Wed Jan 03, 2018 5:03 pm
by TheSAguy
Hey Guys!

I please need your help to implement this logic in my mod - Bio Industries.

What I do in the mod, is have one have the ability to plant seedlings that will grow into trees.
Since different terrains grow different type of trees and the trees have different probabilities of growing in each terrain, I need to check the terrain you plant the seedling in agains ta table and then randomly choose a tree to grow there, based on the trees weight/probability to grow in that type of terrain.

Currently I'm just doing a random select, but thanks to Darkfrei, I actually have a table to each terrain and the trees with weights/probablilitys per terrain. See thread here.

So, I've attached my tree table. (trees-and-terrains.lua)

I need to basically replace the code below in the "control_tree.lua" file, with the stuff from the other link you sent me... seems so simple :)
Code
I'd appreciate it if someone could help me out a little with this, it's just outside of my coding skills. And also possibly looking at the rest of the code, making sure I'm not messing something up...

Thanks!

Re: Random with probability

Posted: Wed Jan 03, 2018 8:03 pm
by darkfrei

Code: Select all

terrains = require("trees-and-terrains")

function summ_weight (tabl)
  local summ = 0
  for i, tree_weights in pairs (tabl) do
    if (type (tree_weights) == "table") and tree_weights.weight then
      summ = summ + tree_weights.weight
    end
  end
  return summ
end

function tree_from_max_index_tabl (max_index, tabl)
  local rnd_index = math.random (max_index)
  for tree_name, tree_weights in pairs (tabl) do
    if (type (tree_weights) == "table") and tree_weights.weight then
      rnd_index = rnd_index - tree_weights.weight
      if rnd_index <= 0 then
        return tree_name
      end
    end
  end
  return nil
end

function random_tree (surface, position)
  local tile = surface.get_tile(position.x, position.y)
  local tile_name = tile.name
  if is_value_as_index_in_table (tile_name, terrains) then
    local trees_table = terrains[tile_name]
    local max_index = summ_weight(trees_table)
    return tree_from_max_index_tabl (max_index, trees_table)
  end
end
So, you can call this functions like here (you are need surface and position of tile or entity with your seed):

Code: Select all

local tree_name = random_tree (surface, position)
if tree_name then
  local can_be_placed = surface.can_place_entity{name=tree_name, position=position, force = "neutral"}
  if can_be_placed then
    local tree = surface.create_entity{name=tree_name, position=position, force = "neutral"}
  end
end

Re: Random with probability

Posted: Sat Jan 06, 2018 8:04 am
by eradicator
I'm still not sure what exactly your problem with the implementation is, it sounds very straight-foward to me.
If you have a terrain-to-tree-weight mapping table like:

Code: Select all

local tile_to_tree = {
  ["red-desert-3"] = {
    {5 ,"tree-01"},
    {10,"tree-02"},
    {20,"tree-03"},
  }
}
You can easily fetch the tree map for a given tile:

Code: Select all

local tree_weight_map = tile_to_tree[surface.get_tile(position).name]
and then use the fight() function from my above post.
Also with a static map like this you might consider including the total weight into the premade table instead of calculating it each time.
(Your current trees-and-terrains.lua table seems to have a lot of redundancy, which imho just makes it more difficult to read while not gaining any benefit by including tile_name and tree_name for each tree.)

EDIT:
Used some regex magic to reformat the table to an imho more readable format (warning, forum does not use monospace formatting):
tiles and trees