如何解决Rust:用gen_range模拟rand函数
我正在编写一个小程序,该程序从枚举中随机选择一个条目。示例代码:
#[derive(Debug)]
enum SettlementSize {
VILLAGE,TOWN,CITY
}
impl Distribution<SettlementSize> for Standard {
fn sample<R: Rng + ?Sized>(&self,rng: &mut R) -> SettlementSize {
let res = rng.gen_range(0,3);
match res {
0 => SettlementSize::VILLAGE,1 => SettlementSize::TOWN,2 => SettlementSize::CITY,_ => panic!("Unknown value!")
}
}
}
fn get_settlement_size(mut rng: impl RngCore) -> SettlementSize {
let size: SettlementSize = rng.gen();
size
}
现在,我当然要对其进行测试。这就是get_settlement_size
采用rng值的原因。
#[test]
fn random_human_readable() {
let rng = StepRng::new(1,1);
assert_eq!("Town",get_settlement_size(rng).human_readable());
}
不幸的是,这不起作用。当我添加一些printlns时,返回值是:
rng.gen_range(0,3);
始终为0。我将StepRng代码复制到测试模块中,以在其中添加println,然后看到next_u32和next_u64被调用。但是,稍后代码消失在UniformSampler
中,这时我很难理解。我究竟做错了什么?我能否以某种方式保留可测试性(这意味着我可以在脑海中为随机设置固定结果)?
解决方法
你说得对, 很容易模拟 RngCore trait 的原始函数, 但是它们被用来避免低阶位偏差的方式和 模数计算的偏差使得模拟 RngCore 中更复杂的功能。
IMO 解决此问题的最简单方法是在使用 Rng 和要测试的代码之间放置一个层。
所以不是这样:
fn get_settlement_size(mut rng: impl RngCore) -> SettlementSize {
let size: SettlementSize = rng.gen();
size
}
你有
trait RngWrapper {
fn get_settlement_size(&mut self) -> SettlementSize;
}
fn get_settlement_size(rng: &mut impl RngWrapper) -> SettlementSize {
rng.get_settlement_size()
}
现在你的“真实”实现是这样的
impl Distribution<SettlementSize> for Standard {
fn sample<R: Rng + ?Sized>(&self,rng: &mut R) -> SettlementSize {
let res = rng.gen_range(0..3);
match res {
0 => SettlementSize::VILLAGE,1 => SettlementSize::TOWN,2 => SettlementSize::CITY,_ => panic!("Unknown value!"),}
}
}
struct Random<'a,R: RngCore> {
rng: &'a mut R,}
impl<'a,R> RngWrapper for Random<'a,R>
where
R: RngCore,{
fn get_settlement_size(&mut self) -> SettlementSize {
self.rng.gen()
}
}
你的模拟可能看起来像这样:
struct AlwaysTown {}
impl RngWrapper for AlwaysTown {
fn get_settlement_size(&mut self) -> SettlementSize {
SettlementSize::TOWN
}
}
现在您可以测试任何使用 get_settlement_size()
的东西,但您还没有解决 Random<'_,ThreadRng>::get_settlement_size
的测试问题 - 然而,这现在是一个孤立的问题,不需要“能够设置固定结果随机”,你在你的问题中提到 - 相反,我会做一个统计测试 - 每个预期的案例都大致出现预期的次数。
如果你想要这个严谨,那么你需要一些统计数据(我不会放在这里) - 但你应该能够进行一个粗略的测试,在 99.99+% 的时间内通过。
一个完整的例子,除了 RngCore 上的统计测试,在 playground 上。
,您可以通过自己实现RngCore特征来轻松创建模拟Rng。它仅具有4个您需要覆盖的功能,并且您可以对其中3个使用存根实现。这是一个始终返回固定值的版本:
struct ConstantRng(u64);
impl RngCore for ConstantRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
fn next_u64(&mut self) -> u64 {
self.0
}
fn fill_bytes(&mut self,dest: &mut [u8]) {
rand_core::impls::fill_bytes_via_next(self,dest)
}
fn try_fill_bytes(&mut self,dest: &mut [u8]) -> Result<(),rand_core::Error> {
Ok(self.fill_bytes(dest))
}
}
然后您可以在测试中使用它
#[test]
fn random_human_readable() {
let rng = ConstantRng(1);
assert_eq!("Town",get_settlement_size(rng).human_readable());
}
如果您希望在返回的值序列上具有更大的灵活性,可以在RNG中存储值的向量,并在使用时弹出它们。
您可以在playground
上找到有效的示例版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。