# `WeightedRandom`
[🔗](https://github.com/greetingsfellowhumans/weighted_random/blob/main/lib/weighted_random.ex#L1)

## Livebook

The best way to learn is through the interactive [Livebook tutorial](guides/tutorial.livemd)

## Quickstart

### Randomness from outcomes and weights.

```elixir
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:
```elixir
[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

```elixir
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)

```elixir
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)

```

# `preprocess`

```elixir
@spec preprocess(
  WeightedRandom.Utils.Types.outcomes(),
  [WeightedRandom.Utils.Types.weight_spec()],
  WeightedRandom.Utils.Types.opts()
) :: WeightedRandom.Backend.t()
```

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` (`t:atom/0`) - Required. Any module which implements the @behaviour: `WeightedRandom.Backend`.
  This is the core algorithm providing the randomness functionality.

* `:precision` (`t: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`

```elixir
@spec preprocess_p(
  WeightedRandom.Utils.Types.probabilities(),
  WeightedRandom.Utils.Types.opts()
) ::
  WeightedRandom.Backend.t()
```

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` (`t:atom/0`) - Required. Any module which implements the @behaviour: `WeightedRandom.Backend`.
  This is the core algorithm providing the randomness functionality.

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

# `rand`

```elixir
@spec rand(
  outcomes :: WeightedRandom.Utils.Types.outcomes(),
  weights :: [WeightedRandom.Utils.Types.weight_spec()],
  WeightedRandom.Utils.Types.opts()
) :: any()
```

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` (`t: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` (`t:pos_integer/0`) - If used, then instead of returning one random value, will return a list (size == :take) of random values

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

# `rand_p`

```elixir
@spec rand_p(
  WeightedRandom.Utils.Types.probabilities(),
  WeightedRandom.Utils.Types.opts()
) :: any()
```

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` (`t:atom/0`) - Any module which implements the @behaviour: `WeightedRandom.Backend`.
  This is the core algorithm providing the randomness functionality.

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

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

# `take`

```elixir
@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`

```elixir
@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]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
