DigitBinIndex
A DigitBinIndex is a tree-based data structure that organizes a large collection of weighted items to enable highly efficient weighted random selection and removal. It is a specialized tool, purpose-built for scenarios with millions of items where probabilities are approximate and high performance is critical.
The Core Problem
In many simulations, forecasts, or statistical models, one needs to manage a large, dynamic set of probabilities. A common task is to randomly select an item based on its weight (probability), remove it from the set, and repeat this process thousands of times. Doing this efficiently with millions of items is a non-trivial performance challenge.
The use case for which this was originally developed is to perform fast selections in a Wallenius' noncentral hypergeometric distribution. This sequential sampling model is common in complex agent-based simulations, such as mortality models.
How It Works
DigitBinIndex is a radix tree where the path is determined by the decimal digits of the probabilities. This structure allows it to group items into "bins" based on a configurable level of precision.
-
Digit-based Tree Structure: The index builds a tree where each level corresponds to a decimal place. For a probability like
0.543, an item would be placed by traversing the path:root -> child[5] -> child[4] -> child[3]. -
Binning: The node at the end of a path acts as a "bin," holding a list of all individuals whose probabilities are truncated to that value. For example, with a precision of 3, probabilities
0.543,0.5432, and0.5439would all be placed in the0.543bin. -
Accumulated Value Index: Each node in the tree stores the
accumulated_value(the sum of all probabilities beneath it). This is the key to its speed. To select an item, a random number is generated between 0 and the root's total value. The tree is then traversed, "spending" the random number on the accumulated values of the branches until a leaf bin is selected.
Features
- Fast Selection: Weighted random selection is an O(P) operation, where P is the configured precision. This is effectively constant time, independent of the number of items.
- Fast Updates: Adding and removing items are also O(P) operations.
- Configurable Precision: The desired precision can be set during instantiation, allowing you to balance accuracy with performance and memory.
- Memory Efficient: For datasets where many items share the same effective probability (up to the chosen precision), this structure is highly memory efficient.
Choosing the Right Tool: DigitBinIndex vs. General-Purpose Structures
DigitBinIndex is a specialized data structure. Its design makes a deliberate engineering trade-off: it sacrifices a small, controllable amount of precision to gain significant improvements in speed and memory usage for its target use case.
The standard, general-purpose tool for this type of problem is a Fenwick Tree (or Binary Indexed Tree), which can store the exact probability for every individual. Here is how they compare conceptually:
| Feature | DigitBinIndex (This Crate) |
Fenwick Tree (General-Purpose) |
|---|---|---|
| Time Complexity | O(P) (P = configured precision) | O(log N) (N = number of individuals) |
| Accuracy | Binned (Approximate) Quantizes probabilities to P decimal places. |
Perfect (Exact) Stores the precise probability for every item. |
| Ideal Data | Empirical probabilities (from medicine, ML, etc.) where precision beyond a few digits is noise. | Theoretical probabilities (from physics, crypto, etc.) where high precision is meaningful. |
This difference in time complexity leads to a dramatic performance gap in practice, as shown by the benchmark results below.
Performance
The following benchmarks compare the time for a single select_and_remove operation for both data structures across a growing number of individuals (N).
| Number of Items (N) | DigitBinIndex Time |
FenwickTree Time |
Speedup Factor |
|---|---|---|---|
| 100,000 | ~10.3 µs | ~5,579 µs (5.58 ms) | ~542x faster |
| 1,000,000 | ~346.1 µs | ~59,041 µs (59.0 ms) | ~171x faster |
| 10,000,000 | ~739.1 µs | ~624,050 µs (624 ms) | ~844x faster |
As the table shows, DigitBinIndex is not just faster; it is orders of magnitude faster for its intended use case.
✅ When to Choose DigitBinIndex
This structure is the preferred choice when your scenario matches these conditions:
- You have a very large number of items (
Nis in the millions). - Performance is critical.
- Your probabilities are approximate. If your weights come from empirical data, simulations, or machine learning models, the precision beyond a few decimal places is often meaningless.
- Many items share the same effective probability.
❌ When to Consider an Alternative (like a Fenwick Tree)
You should use a more general-purpose data structure if:
- You require perfect, lossless precision. If all your items have unique probabilities that only differ at a high decimal place (e.g., the 15th digit), you would need to set
Pso high that the performance and memory benefits would be lost.
Usage
First, add digit-bin-index to your Cargo.toml:
[]
= "0.1.0" # Replace with the actual version
= "0.15" # This structure relies on the Decimal type
= "0.9"
Then, you can use it in your project:
use DigitBinIndex;
use Decimal;
License
This project is licensed under the MIT License.