#分布 #属性 #标准 #随机 #类型 # #可派生

standard-dist

为类型提供可派生的随机分布

1个稳定版本

1.0.0 2021年5月18日

#1832进程宏

每月下载量 26

MPL-2.0 许可证

25KB
316

standard-dist

一个属性宏,用于为Rust类型创建Standard分布


lib.rs:

standard-dist是一个库,通过派生宏自动为您自己的类型派生一个rand标准分布。

使用示例

use rand::distributions::Uniform;
use standard_dist::StandardDist;

// Select heads or tails with equal probability
#[derive(Debug, Clone, Copy, PartialEq, Eq, StandardDist)]
enum Coin {
Heads,
Tails,
}

// Flip 3 coins, independently
#[derive(Debug, Clone, Copy, PartialEq, Eq, StandardDist)]
struct Coins {
first: Coin,
second: Coin,
third: Coin,
}

// Use the `#[distribution]` attribute to customize the distribution used on
// a field
#[derive(Debug, Clone, Copy, PartialEq, Eq, StandardDist)]
struct Die {
#[distribution(Uniform::from(1..=6))]
value: u8
}

// Use the `#[weight]` attribute to customize the relative probabilities of
// enum variants
#[derive(Debug, Clone, Copy, PartialEq, Eq, StandardDist)]
enum D20 {
#[weight(18)]
Normal,

Critical,
CriticalFail,
}

rand通过Distribution特质生成类型化的随机值,该特质使用一个随机源来产生给定类型的值。值得注意的是Standard分布,它是产生特定类型随机值的无状态“默认”方式。例如

  • 对于整数,它从这个整型类型的所有可能值中随机选择
  • 对于布尔值,它以50/50的概率选择true或false
  • 对于Option<T>,它以50/50的概率选择NoneSome,并使用Standard随机填充内部Some值。

结构体

当您为您的自定义结构体派生StandardDist时,它将创建一个impl Distribution<YourStruct> for Standard实现,允许您通过Rng::gen创建结构体的随机实例。此实现将使用Standard分布来填充您类型的所有字段。

use standard_dist::StandardDist;

#[derive(StandardDist)]
struct SimpleStruct {
coin: bool,
percent: f64,
}

let mut heads = 0;

for _ in 0..2000 {
let s: SimpleStruct = rand::random();
assert!(0.0 <= s.percent);
assert!(s.percent < 1.0);
if s.coin {
heads += 1;
}
}

assert!(900 < heads, "heads: {}", heads);
assert!(heads < 1100, "heads: {}", heads);

自定义分布

您可以使用属性 #[distribution] 来自定义任何字段的分布。

use std::collections::HashMap;
use standard_dist::StandardDist;
use rand::distributions::Uniform;

#[derive(StandardDist)]
struct Die {
#[distribution(Uniform::from(1..=6))]
value: u8
}

let mut counter: HashMap<u8, u32> = HashMap::new();

for _ in 0..6000 {
let die: Die = rand::random();
*counter.entry(die.value).or_insert(0) += 1;
}

assert_eq!(counter.len(), 6);

for i in 1..=6 {
let count = counter[&i];
assert!(900 < count, "{}: {}", i, count);
assert!(count < 1100, "{}: {}", i, count);
}

枚举类型

当应用于枚举类型时,实现将随机选择一个变体(其中每个变体的概率相等),然后以与结构体相同的方式填充该变体的所有字段。枚举变体字段可以像结构体字段一样通过 #[distribution] 应用自定义分布。

use standard_dist::StandardDist;

#[derive(PartialEq, Eq, StandardDist)]
enum Coin {
Heads,
Tails,
}

let mut heads = 0;

for _ in 0..2000 {
let coin: Coin = rand::random();
if coin == Coin::Heads {
heads += 1;
}
}

assert!(900 < heads, "heads: {}", heads);
assert!(heads < 1100, "heads: {}", heads);

权重

可以使用属性 #[weight] 对枚举变体进行加权,使其在随机选择时相对更可能或不太可能。权重为 0 表示该变体将永远不会被选中。任何未标记的变体将具有权重 1。

use standard_dist::StandardDist;

#[derive(StandardDist)]
enum D20 {
#[weight(18)]
Normal,

CriticalHit,
CriticalMiss,
}

let mut crits = 0;

for _ in 0..20000 {
let roll: D20 = rand::random();
if matches!(roll, D20::CriticalHit) {
crits += 1;
}
}

assert!(900 < crits, "crits: {}", crits);
assert!(crits < 1100, "crits: {}", crits);

高级自定义分布

分布类型

您可以可选地显式指定分布的类型;在某些情况下,使用泛型类型时这可能是有必要的。

use std::collections::HashMap;
use standard_dist::StandardDist;
use rand::distributions::Uniform;

#[derive(StandardDist)]
struct Die {
#[distribution(Uniform<u8> = Uniform::from(1..=6))]
value: u8
}

let mut counter: HashMap<u8, u32> = HashMap::new();

for _ in 0..6000 {
let die: Die = rand::random();
*counter.entry(die.value).or_insert(0) += 1;
}

assert_eq!(counter.len(), 6);

for i in 1..=6 {
let count = counter[&i];
assert!(900 < count, "{}: {}", i, count);
assert!(count < 1100, "{}: {}", i, count);
}

分布缓存

在某些情况下,您可能希望缓存一个 Distribution 实例以供重用。许多分布会在构造时进行一些初始计算,因此重用现有分布而不是每次生成值时都重新创建它们可以帮助提高性能。《code>standard-dist 提供了两种缓存分布的方法:staticonce。一个 static 分布存储为全局静态变量;这是首选选项,但它要求初始化器在 const 上下文中可用。一个 once 分布存储在 once_cell::sync::OnceCell 中;它在使用时首次初始化,然后在后续调用中重用。

在任何情况下,都通过在类型前加 oncestatic 来指定缓存策略。必须指定类型才能使用缓存策略。

use std::collections::HashMap;
use std::time::{Instant, Duration};
use standard_dist::StandardDist;
use rand::prelude::*;
use rand::distributions::Uniform;

#[derive(StandardDist)]
struct Die {
#[distribution(Uniform::from(1..=6))]
value: u8
}

#[derive(StandardDist)]
struct CachedDie {
#[distribution(once Uniform<u8> = Uniform::from(1..=6))]
value: u8
}

fn timed<T>(task: impl FnOnce() -> T) -> (T, Duration) {
let start = Instant::now();
(task(), start.elapsed())
}

// Count the 6s
let mut rng = StdRng::from_entropy();

let (count, plain_die_duration) = timed(|| (0..600000)
.map(|_| rng.gen())
.filter(|&Die{ value }| value == 6)
.count()
);

assert!(90000 < count);
assert!(count < 110000);

let (count, cache_die_duration) = timed(|| (0..600000)
.map(|_| rng.gen())
.filter(|&CachedDie{ value }| value == 6)
.count()
);

assert!(90000 < count);
assert!(count < 110000);

assert!(
cache_die_duration < plain_die_duration,
"cache: {:?}, plain: {:?}",
cache_die_duration,
plain_die_duration,
);

请注意,除非您正在生成大量随机对象,否则由于初始化单元格的前期成本,使用 cell 很可能是一种优化。如果性能是问题,请确保对您的特定用例进行基准测试。

依赖项

~2MB
~44K SLoC