Random with probability

Place to get help with not working mods / modding interface.
User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2905
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Random with probability

Post 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?
ratchetfreak
Filter Inserter
Filter Inserter
Posts: 952
Joined: Sat May 23, 2015 12:10 pm
Contact:

Re: Random with probability

Post 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.
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5211
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Random with probability

Post 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
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2905
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: Random with probability

Post 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.
User avatar
Klonan
Factorio Staff
Factorio Staff
Posts: 5304
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

Re: Random with probability

Post 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
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5211
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Random with probability

Post 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.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Random with probability

Post 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!
Attachments
control_tree.lua
Tree Growing Control File
(16.68 KiB) Downloaded 88 times
trees-and-terrains.lua
Tree Table
(29.45 KiB) Downloaded 101 times
User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2905
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: Random with probability

Post 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
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5211
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Random with probability

Post 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
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
Post Reply

Return to “Modding help”