Rust Weighted Choices
A weighted choice is a random pick where each outcome carries a weight. A bigger weight means a bigger share of the total.
Weights bend the odds
WeightedIndex takes one weight per outcome. Book weighs 6 of 10 here, so it wins this draw.
let mut rng = StdRng::seed_from_u64(9);
let wheel = WeightedIndex::new([1, 3, 6]).unwrap();
let prizes = ["sticker", "pencil", "book"];
println!("draw = {}", prizes[wheel.sample(&mut rng)]);Weights can ride on the items
choose_weighted reads the weight off each item. The apple outweighs the cake five to one.
let mut rng = StdRng::seed_from_u64(5);
let snacks = [("apple", 5), ("cake", 1)];
let pick = snacks.choose_weighted(&mut rng, |s| s.1).unwrap();
println!("snack = {}", pick.0);A coin with exact odds
Bernoulli is a coin that lands true with the chance you set. Here it rains on about one day in four.
let mut rng = StdRng::seed_from_u64(11);
let rain = Bernoulli::new(0.25).unwrap();
let mut wet = 0;
for _ in 0..7 {
if rain.sample(&mut rng) {
wet += 1;
}
}
println!("wet days = {wet}");Pick indexes with no repeats
index::sample draws distinct indexes from a range. Nothing can be picked twice.
let mut rng = StdRng::seed_from_u64(30);
let picks = rand::seq::index::sample(&mut rng, 10, 3);
println!("picks = {:?}", picks.into_vec());Shuffle only the front
partial_shuffle shuffles just the front of a slice. Dealers use it to deal the top cards.
let mut rng = StdRng::seed_from_u64(30);
let mut deck = [1, 2, 3, 4, 5, 6, 7, 8];
let dealt = deck.partial_shuffle(&mut rng, 3);
println!("front = {:?}", dealt.0);Example
// Cargo.toml: rand = "0.10"
use rand::distr::weighted::WeightedIndex;
use rand::distr::{Bernoulli, Distribution};
use rand::prelude::*;
use rand::rngs::StdRng;
use rand::SeedableRng;
fn main() {
let mut rng = StdRng::seed_from_u64(42);
let prizes = ["sticker", "pencil", "book"];
let wheel = WeightedIndex::new([1, 3, 6]).unwrap();
for n in 1..=6 {
println!("spin {n} = {}", prizes[wheel.sample(&mut rng)]);
}
let rain = Bernoulli::new(0.25).unwrap();
let mut wet_days = 0;
for _ in 0..7 {
if rain.sample(&mut rng) {
wet_days += 1;
}
}
println!("wet days this week = {wet_days}");
let names = ["ada", "bo", "cy", "di", "ed", "flo"];
let mut team = Vec::new();
for name in names.choose_multiple(&mut rng, 2) {
team.push(*name);
}
println!("team = {:?}", team);
let mut deck = [1, 2, 3, 4, 5, 6, 7, 8];
let dealt = deck.partial_shuffle(&mut rng, 3);
println!("dealt = {:?}", dealt.0);
}Key points
- Weight: a share of the total, not a percent.
- Weights 1, 3 and 6 mean chances of a tenth, three tenths and six tenths
- WeightedIndex::new checks the weights.
- It returns an error when every weight is zero
- Use random_ratio(a, b) for exact odds in whole numbers.
- It answers true a times in b, over the long run
- Use index::sample to draw distinct positions from a range
- Use sample on a slice to draw distinct items.
- Nothing repeats
In this example, one seeded generator drives every draw, so the page can print exact results. The wheel spins six times. Book carries six tenths of the weight, and it lands three times. Pencil lands twice and sticker not at all. Small samples still wobble, and that wobble is normal. The Bernoulli coin then rains on three of seven days. Last, the programme picks a two-name team with no repeats. It also deals three shuffled cards off the front of the deck.
Note
Weighted draws stay honest only while the weights stay honest.
Book lands most often here because the weight says so, not because the wheel is fair
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A loot table must drop the rare sword rarely and the common apple often. What fits best?