Choice - Weighted Random Selector
Created by Hiroya Aramaki (Makihiro)
What is Weighted Random Selector ?
Weighted Random Selector is an algorithm for randomly selecting elements based on their weights.
For example.
- Drop items based on rarity.
- Events that occur with a certain probability
It can be used to determine things with probability.
Choice is a library that was created to make it easier to implement.
// This is the simplest usage.
var randomSelectedItem = items
.ToWeightedSelector(item => item.weight)
.SelectItemWithUnityRandom();
Great introduction article on Weighted Random Select: https://blog.bruce-hill.com/a-faster-weighted-random-choice
Table of Contents
📥 Installation
Download any version from releases.
Releases: https://github.com/mackysoft/Choice/releases
Install via git URL
Or, you can add this package by opening PackageManager and entering
https://github.com/mackysoft/Choice.git?path=Assets/MackySoft/MackySoft.Choice
from the Add package from git URL
option.
Install via Open UPM
Or, you can install this package from the Open UPM registry.
More details here.
openupm add com.mackysoft.choice
🔰 Usage
// To use Choice, add this namespace.
using MackySoft.Choice;
public class WeightedItem {
public string id;
public float weight;
}
public WeightedItem SelectItem () {
// Prepare weighted items.
var items = new WeightedItem[] {
new WeightedItem { id = "🍒", weight = 8f },
new WeightedItem { id = "🍏", weight = 4f },
new WeightedItem { id = "🍍", weight = 0f },
new WeightedItem { id = "🍇", weight = 6f },
new WeightedItem { id = "🍊", weight = -1f }
};
// Create the WeightedSelector.
var weightedSelector = items.ToWeightedSelector(item => item.weight);
// The probability of each item being selected,
// 🍒 is 44%, 🍏 is 22%, and 🍇 is 33%.
// 🍍 and 🍊 will never be selected because their weights are less or equal to 0.
return weightedSelector.SelectItemWithUnityRandom();
// Same as weightedSelector.SelectItem(UnityEngine.Random.value);
}
ToWeightedSelector
Overloads
The ToWeightedSelector
method has many overloads and can be used for a variety of patterns.
from weighted entry pattern
public struct ItemEntry {
public Item item;
public float weight;
}
public IWeightedSelector<Item> WeightedEntryPattern () {
var entries = new ItemEntry[] {
new ItemEntry { item = new Item { id = "🍒" }, weight = 1f },
new ItemEntry { item = new Item { id = "🍏" }, weight = 5f },
new ItemEntry { item = new Item { id = "🍍" }, weight = 3f }
};
// Create a WeightedSelector by selecting item and weight from entry respectively.
return entries.ToWeightedSelector(
itemSelector: entry => entry.item,
weightSelector: entry => entry.weight
);
}
from weighted item pattern
public class WeightedItem {
public string id;
public float weight;
}
public IWeightedSelector<WeightedItem> WeightedItemPattern () {
var items = new WeightedItem[] {
new WeightedItem { id = "🍒", weight = 1f },
new WeightedItem { id = "🍏", weight = 5f },
new WeightedItem { id = "🍍", weight = 3f }
};
// Create a WeightedSelector using the weight of the WeightedItem.
return fromWeightedItem = items.ToWeightedSelector(weightSelector: item => item.weight);
}
from Dictionary<TItem,float>
pattern
public class Item {
public string id;
}
public IWeightedSelector<Item> DictionaryPattern () {
// This need a Dictionary<TItem,float>. (Strictly speaking, IEnumerable<KeyValuePair<TItem,float>>)
var dictionary = new Dictionary<Item,float>(
{ new Item { id = "🍒" }, 1f },
{ new Item { id = "🍏" }, 5f },
{ new Item { id = "🍍" }, 3f }
);
// Create a WeightedSelector with the dictionary key as item and value as weight.
return dictionary.ToWeightedSelector();
}
LINQ
Since the ToWeightedSelector
method is defined as an extension of IEnumerable<T>
, it can be connected from the LINQ query operators.
var randomSelectedItem = items
.Where(item => item != null) // null check
.ToWeightedSelector(item => item.weight)
.SelectItemWithUnityRandom();
Algorithms
When creating a WeightedSelector, you can specify the IWeightedSelectMethod
.
var weightedSelector = items.ToWeightedSelector(
item => item.weight,
WeightedSelectMethod.Binary // Use the binary search algorithm.
);
All ToWeightedSelector
methods can specify IWeightedSelectMethod
.
If this is not specified, the linear scan algorithm will be used automatically.
Linear Scan (WeightedSelectMethod.Linear
)
The simplest algorithm that walks linearly along the weights.
This method is an O(n)
operation, where n
is number of weights.
Binary Search (WeightedSelectMethod.Binary
)
The binary search algorithm that is faster than linear scan by preprocessing to store the current sum of weights.
It has an additional storage cost of O(n)
, but is accelerated by up to O(log(n))
for each selection, where n
is number of weights.
Alias Method (WeightedSelectMethod.Alias
)
The fastest algorithm.
It takes O(n)
run time to set up, but the selection is performed in O(1)
run time,
where n
is number of weights.
Therefore, this is a very effective algorithm for selecting multiple items.
Speed Measurement
1 items
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0104ms | 0.0055ms | 0.0081ms | 0.0393ms | 0.3408ms |
Binary Search | 0.0091ms | 0.0083ms | 0.0126ms | 0.0659ms | 0.5944ms |
Alias Method | 0.0069ms | 0.0065ms | 0.01ms | 0.0459ms | 0.4094ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0155ms | 0.0064ms | 0.0077ms | 0.0381ms | 0.353ms |
Binary Search | 0.0077ms | 0.008ms | 0.0123ms | 0.0659ms | 0.5919ms |
Alias Method | 0.0062ms | 0.0065ms | 0.01ms | 0.0462ms | 0.41ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.009ms | 0.0053ms | 0.0081ms | 0.0378ms | 0.3388ms |
Binary Search | 0.0073ms | 0.0079ms | 0.0129ms | 0.0653ms | 0.5927ms |
Alias Method | 0.0054ms | 0.0062ms | 0.0104ms | 0.0461ms | 0.4194ms |
10 items
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0113ms | 0.0077ms | 0.0182ms | 0.1219ms | 1.19ms |
Binary Search | 0.0109ms | 0.0114ms | 0.0237ms | 0.158ms | 1.4975ms |
Alias Method | 0.0136 | 0.022ms | 0.0151ms | 0.0601ms | 0.5041ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.012ms | 0.0072ms | 0.0174ms | 0.1272ms | 1.1738ms |
Binary Search | 0.0095ms | 0.0099ms | 0.023ms | 0.16ms | 1.5503ms |
Alias Method | 0.0141ms | 0.0104ms | 0.0148ms | 0.0618ms | 0.5235ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0095ms | 0.009ms | 0.0179ms | 0.1216ms | 1.1503ms |
Binary Search | 0.0096ms | 0.0103ms | 0.0225ms | 0.1572ms | 1.4991ms |
Alias Method | 0.0129ms | 0.0105ms | 0.015ms | 0.0607ms | 0.5176ms |
100 items
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0201ms | 0.024ms | 0.0822ms | 0.741ms | 7.2211ms |
Binary Search | 0.0212ms | 0.0211ms | 0.0433ms | 0.3118ms | 2.6434ms |
Alias Method | 0.0717ms | 0.0364ms | 0.0395ms | 0.086ms | 0.5462ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0231ms | 0.0247ms | 0.0855ms | 0.7027ms | 7.0025ms |
Binary Search | 0.0224ms | 0.0231ms | 0.0441ms | 0.2776ms | 2.6521ms |
Alias Method | *0.039ms | 0.0358ms | 0.0405ms | 0.0861ms | 0.5561ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0194ms | 0.0232ms | 0.0892ms | 0.7582ms | 7.1886ms |
Binary Search | 0.0206ms | 0.0218ms | 0.0447ms | 0.2804ms | 2.6375ms |
Alias Method | 0.0376ms | 0.0381ms | 0.0413ms | 0.0871ms | 0.5728ms |
1000 items
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.1147ms | 0.1672ms | 0.7792ms | 6.7539ms | 66.8329ms |
Binary Search | 0.1205ms | 0.1183ms | 0.1504ms | 0.4758ms | 3.7755ms |
Alias Method | 0.2783ms | 0.2722ms | 0.2925ms | 0.3238ms | 0.7824ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.1068ms | 0.1717ms | 0.8331ms | 6.8282ms | 68.455ms |
Binary Search | 0.1217ms | 0.1173ms | 0.1499ms | 0.5026ms | 3.7627ms |
Alias Method | 0.2785ms | 0.2889ms | 0.2876ms | 0.3318ms | 0.908ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.102ms | 0.1636ms | 0.7271ms | 6.743ms | 66.4393ms |
Binary Search | 0.1241ms | 0.1208ms | 0.1501ms | 0.5216ms | 4.0165ms |
Alias Method | 0.2782ms | 0.2755ms | 0.2777ms | 0.3454ms | 0.8068ms |
10000 items
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 1.1885ms | 1.7971ms | 8.0482ms | 69.1749ms | 664.8696ms |
Binary Search | 1.3329ms | 1.3181ms | 1.3454ms | 1.7735ms | 6.1215ms |
Alias Method | 2.8859ms | 2.8719ms | 2.8832ms | 2.9779ms | 3.4764ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 1.1676ms | 1.6953ms | 8.0905ms | 70.1629ms | 668.3197ms |
Binary Search | 1.3118ms | 1.3361ms | 1.3407ms | 1.786ms | 6.1105ms |
Alias Method | 2.8833ms | 2.934ms | 2.951ms | 2.9845ms | 3.6259ms |
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 1.3098ms | 1.9826ms | 8.063ms | 68.9301ms | 666.9364ms |
Binary Search | 1.4456ms | 1.3787ms | 4.6233ms | 1.7861ms | 6.0783ms |
Alias Method | 2.9751ms | 2.9144ms | 2.9236ms | 2.9851ms | 3.5149ms |
10000 items without setup
Iterations | 1 | 10 | 100 | 1000 | 10000 |
---|---|---|---|---|---|
Linear Scan | 0.0207ms | 0.7364ms | 6.5433ms | 67.3963ms | 671.3184ms |
Binary Search | 0.0015ms | 0.0055ms | 0.0492ms | 0.496ms | 4.828ms |
Alias Method | 0.0005ms | 0.0011ms | 0.0066ms | 0.0579ms | 0.5559ms |
📔 Author Info
Hiroya Aramaki is a indie game developer in Japan.
- Blog: https://mackysoft.net/blog
- Twitter: https://twitter.com/makihiro_dev
📜 License
This library is under the MIT License.