WAM (wam v0.2.0)

Copy Markdown

Walker's Alias Method for weighted random sampling in O(1).

The probability table

probs[i] is the probability of keeping bucket i, so a draw reads

if rng < probs[i], do: i, else: aliases[i]

with rng expected in [0, 1).

This orientation is what makes a weight of 0 unreachable by construction rather than merely improbable. A zero-weight atom is always paired away, so its probs entry is exactly 0.0, and rng < 0.0 is false for any rng >= 0 — the guarantee holds whatever the generator's upper bound turns out to be and whatever a caller passes. Code that mutes content by giving it a weight of 0 can rely on that.

Reading a slot without drawing is at/3, not get/4 with a sentinel rng.

Summary

Types

t()

@type t() :: %WAM{
  aliases: tuple(),
  probs: tuple(),
  size: non_neg_integer(),
  values: tuple()
}

Functions

at(wam, index, default \\ nil)

@spec at(t(), non_neg_integer(), term()) :: term()

fetch(wam, index, rng)

@spec fetch(t(), non_neg_integer(), float()) ::
  {:ok, term()} | {:error, :invalid_index}

fetch_at(wam, index)

@spec fetch_at(t(), non_neg_integer()) :: {:ok, term()} | {:error, :invalid_index}

Reads the value stored at index, without drawing.

Use this instead of calling get/4 with an rng chosen to defeat the alias — that trick depends on how probs is oriented and breaks silently when it changes.

get(wam, index, rng, default \\ nil)

@spec get(t(), non_neg_integer(), float(), term()) :: term()

index(wam, index, rng)

@spec index(t(), non_neg_integer(), float()) ::
  {:ok, non_neg_integer()} | {:error, :invalid_index}

new(data)

@spec new(list()) :: t()
@spec new(map()) :: t()

new(values, weights)

@spec new(list(), list()) :: t()