WeightedRandom (weighted_random v1.0.0-alpha.1)

Copy Markdown View Source

Livebook

The best way to learn is through the interactive Livebook tutorial

Quickstart

Randomness from outcomes and weights.

outcomes = 0..10
weight = %{target: 5, weight: 20}
r = WeightedRandom.preprocess(0..100, [weight])
count = 8

[4, 5, 5, 5, 4, 1, 5, 5] = WeightedRandom.take(r, count)

Alternately, if you care less about performance, and only need to do it once:

[5, 5, 2] = WeightedRandom.rand(0..10, [%{target: 5, weight: 20}], take: 3)

But please note the algorithm is optimized to take longer ( O(n) ) during preprocessing, in order to be very fast ( O(1) ) during the sampling step. So it will be far better to use WeightedRandom.preprocess/3 once, and WeightedRandom.take/2 many times. rand/3 preprocesses EVERY time it is called.

Randomness from probabilities

Mirroring the functions above, we can also use the *_p functions to use probabilities instead of outcomes + weights

probabilities = [0.01, 0.01, 0.01, 0.95, 0.01, 0.01]
r = WeightedRandom.preprocess_p(probabilities)
count = 5

[3, 3, 3, 3, 3] = WeightedRandom.take(r, count)

Or, in rand form (with the same disclaimer)

probabilities = [0.01, 0.01, 0.01, 0.95, 0.01, 0.01]
count = 5

[3, 3, 3, 3, 3] = WeightedRandom.rand_p(r, count)

Summary

Functions

Given a non-empty list (or range) of possible outcomes, and a list of weight maps, build a struct that can later be used for very fast random sampling.

Given a non-empty list of percentages (floats from 0.0 - 1.0), build the struct.

Returns a random value based on the weights given. If you need a lot of random numbers over time, this is suboptimal and you should use preprocess + take instead.

similar to rand/3 but instead of a list of outcomes, and a list of weights, rand_p/3 accepts a list of probability floats. If you need a lot of random numbers over time, this is suboptimal and you should use preprocess + take instead.

Given a WeightedRandom struct, return a single random value.

Given a WeightedRandom struct, return a list of random values

Functions

preprocess(outcomes, weights, opts \\ [])

Given a non-empty list (or range) of possible outcomes, and a list of weight maps, build a struct that can later be used for very fast random sampling.

Next, pass the resulting struct into WeightedRandom.take/2 to get rand

Examples

iex> r = WeightedRandom.preprocesses(0..10, [%{target: 2, amount: 1000}])
iex> li = WeightedRandom.take(r, 5)
[2, 2, 2, 2, 2]

Supported options:

  • :backend (atom/0) - Required. Any module which implements the @behaviour: WeightedRandom.Backend. This is the core algorithm providing the randomness functionality.

  • :precision (pos_integer/0) - The number of decimal places to use when rounding. Leave nil for no rounding.

  • :outcome_type - When you take a random sample, will it return the index of an outcome, or the value?

    • index: pick random indices from the list of outcomes. For example if your outcomes are 125..130 then the results will be between 0 and 5.
    • value: pick random values from the list of outcomes. For example if your outcomes are 125..130 then the results will be between 125 and 130.

    The default value is :index.

preprocess_p(probabilities, opts \\ [])

Given a non-empty list of percentages (floats from 0.0 - 1.0), build the struct.

Next, pass the resulting struct into WeightedRandom.take/2 to get rand

Examples

iex> r = WeightedRandom.preprocess_p([0.01, 0.01, 0.98])
iex> li = WeightedRandom.take(r, 5)
[2, 2, 2, 2, 2]

Supported options:

  • :backend (atom/0) - Required. Any module which implements the @behaviour: WeightedRandom.Backend. This is the core algorithm providing the randomness functionality.

  • :precision (pos_integer/0) - The number of decimal places to use when rounding. Leave nil for no rounding.

rand(outcomes, weights, opts \\ [])

Returns a random value based on the weights given. If you need a lot of random numbers over time, this is suboptimal and you should use preprocess + take instead.

Supported options:

  • :precision (pos_integer/0) - The number of decimal places to use when rounding. Leave nil for no rounding.

  • :outcome_type - When you take a random sample, will it return the index of an outcome, or the value?

    • index: pick random indices from the list of outcomes. For example if your outcomes are 125..130 then the results will be between 0 and 5.
    • value: pick random values from the list of outcomes. For example if your outcomes are 125..130 then the results will be between 125 and 130.

    The default value is :index.

  • :take (pos_integer/0) - If used, then instead of returning one random value, will return a list (size == :take) of random values

  • :backend (atom/0) - Any module which implements the @behaviour: WeightedRandom.Backend. This is the core algorithm providing the randomness functionality.

rand_p(probabilities, opts \\ [])

similar to rand/3 but instead of a list of outcomes, and a list of weights, rand_p/3 accepts a list of probability floats. If you need a lot of random numbers over time, this is suboptimal and you should use preprocess + take instead.

Supported options:

  • :backend (atom/0) - Any module which implements the @behaviour: WeightedRandom.Backend. This is the core algorithm providing the randomness functionality.

  • :precision (pos_integer/0) - The number of decimal places to use when rounding. Leave nil for no rounding.

  • :take (pos_integer/0) - If used, then instead of returning one random value, will return a list (size == :take) of random values

take(processed_struct)

@spec take(WeightedRandom.Backend.t()) :: any()

Given a WeightedRandom struct, return a single random value.

Examples

iex> r = WeightedRandom.preprocesses(0..10, [%{target: 2, amount: 1000}])
iex> li = WeightedRandom.take(r)
2

take(processed_struct, count)

@spec take(WeightedRandom.Backend.t(), count :: integer()) :: list()

Given a WeightedRandom struct, return a list of random values

Examples

iex> # Make the item at index 2 1000x more likely than any other single index.
iex> r = WeightedRandom.preprocesses(0..10, [%{target: 2, amount: 1000}])
iex> li = WeightedRandom.take(r, 3)
[2, 2, 2]