生成随机值

应用场景

生成随机数最显著的应用场景莫过于一些随机业务,如公司年会的抽奖、各类彩票业务、邮箱新用户生成密钥等。

但对于非随机业务来说,随机数的生成也有罕见。多见于瞬时负载压力,数据大批量处理等方面的业务。比如:

  • 多客户端多线程并发类似的业务。在高峰期间,会导致应用服务器、数据库服务器的瞬时负载率超高。将会造成任务处理的延迟,必须进行优化以降低负载。在这种情况下,有一种解决方案即为分析梳理业务,进行随机休眠处理,这样可将执行散列到不同的时间段。
  • 数据预热业务。当有大量数据不能离线预热,必须要线上预热的时候,势必造成资源的严重紧张,甚至打垮服务器。所以采用随机预热方式,使数据逐渐预热。预热成功后,在取消随机预热。
  • 批量的缓存处理。当大批量缓存同时建立,又批量失效,导致缓存建立不分散,对服务端瞬时产生压力。可以通过将部分缓存失效时间随机延长几分钟即可,分散批量建立和失效的压力。
  • 也可用于重复提交方面的解决方案、浏览器缓存处理等等。

生成随机数的应用场景非常广阔,在此不一一赘述。而当前技术潮流中,和生成随机数功能结合最紧密的,莫过于大红大紫的区块链技术。

在区块链及其数字证券领域中,随机数是关键的技术要点,其直接与区块链及其数字证券领域中最重要和最基本的安全保障方面息息相关。

crate 介绍

rand

rand crate 是 Rust 生态中的一个随机数生成工具。其提供了如下功能:

  • 生成随机数;
  • 将生成的随机数进行类型转换、分发;
  • 一些与随机性相关的算法。

rand crate 是由一系列 crate 组成的,rand crate 提供了主用户界面。如果需要额外的分发类型,可以使用 rand_distr crate 或者 statrs crate 来补充。具体结构关系如表 3.1-1 所示。

getrandom ┐
          └ rand_core ┐
                      ├ rand_chacha ┐
                      ├ rand_hc     ┤
                      ├ rand_pcg    ┤
                      └─────────────┴ rand ┐
                                           ├ rand_distr
                                           └ statrs

表 3.1-1

rand_distr

rand_distr crate 实现了诸多概率分布类型,如均匀分布、正态分布(Normal distribution)、柯西分布(Cauchy distribution)等。rand_distr crate 是 rand::distributions 模块的一个超级集合,提供了以下概率分布:

  • 线性增长相关(例如误差、偏移量等):
    • 正态(Normal)分布,以标准正态(StandardNormal)为原语
    • 柯西(Cauchy)分布
  • 伯努利试验相关(给定概率的是/否事件):
    • 二项(Binomial)分布
  • 指数增长相关(例如价格、收入、人口等):
    • 对数正态(LogNormal)分布
  • 给定条件下独立事件的发生率相关:
    • 帕雷托(Pareto)分布
    • 泊松(Poisson)分布
    • 指数(Exponential)分布,以结构体 Exp1 为原语
    • 韦布尔(Weibull)分布
  • 伽马分布、导出分布:
    • 伽马(Gamma)分布
    • 卡方(ChiSquared)分布
    • 学生-T(StudentT)分布
    • 费歇尔-F(FisherF)分布
  • 三角学分布:
    • Beta 分布
    • 三角(Triangular)分布
  • 多元概率分布:
    • 狄利克雷(Dirichlet)分布
    • UnitSphere 分布
    • UnitBall 分布
    • UnitCircle 分布
    • UnitDisc 分布
  • 其它分布:
    • 逆高斯(InverseGaussian)分布
    • 正态逆高斯(NormalInverseGaussian)分布

如上表 3.1-1 所示,rand_distr crate 是对 rand crate 的补充。

实例实践

生成随机数

rand-badge cat-science-badge

在随机数生成器 rand::Rng 的帮助下,通过 rand::thread_rng 生成随机数。可以开启多个线程,每个线程都有一个初始化的生成器。整数在其类型范围内均匀分布,浮点数是从 0 均匀分布到 1,但不包括 1。

  • rand::RngRngCore 上自动实现的扩展 trait,为抽样取值和其它便捷方法实现了高层次的泛型方法。
  • rand::thread_rng:创建随机数生成器线程的函数。调用后,由系统创建延迟初始化的本地线程。随机生成器线程将用于方法链(method chaining)样式,如 thread_rng().gen::<i32>()。或本地缓存,如 let mut rng = thread_rng();。由 Default trait 调用,等效于 ThreadRng::default()

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand::Rng; // `RngCore` 上自动实现的扩展 trait,实现了高层次的泛型方法

fn main() {
    let mut rng = rand::thread_rng(); // 由系统创建的本地线程,是延迟初始化的随机数生成器

    let n1: u8 = rng.gen(); // 返回一个支持标准分布的随机值
    let n2: u16 = rng.gen();
    
    println!("  u8    随机数: {}", n1);
    println!("  u16   随机数: {}", n2);
    println!("  u32   随机数: {}", rng.gen::<u32>());
    println!("  i32   随机数: {}", rng.gen::<i32>());
    println!("  float 随机数: {}", rng.gen::<f64>());
}

代码第 1 行,我们使用 use 关键字将 rand::Rng 引入作用域。rand::Rng 是在 RngCore trait 上自动实现的扩展 trait,它实现了高层次的泛型方法。

代码第 4 行,由系统创建本地线程,作用为延迟初始化的随机数生成器。

代码第 6,7 行,均为返回支持标准分布的随机值,分别是 u8,u16 的无符号整型。

构建并运行后,结果大抵如图 3.1-1 所示,但具体值和笔者运行结果不一定相同。

rand

图 3.1-1

生成范围内随机数

rand-badge cat-science-badge

使用 Rng::gen_range,在半开放的 [0, 10) 范围内(不包括 10)生成一个随机值。

  • Rng::gen_range:此函数在 [低位,高位] 范围内生成一个随机值。此范围为半开放范围,即包括低位而不包括高位。此函数针对给定范围内仅生成一个样本值的情况进行了优化。另外,此函数为均匀分布类型,如果从相同的范围重复抽样取值,执行速度将会更快。
  • Uniform:样本值均匀地分布在两个界限之间的结构体。Uniform::newUniform::new_inclusive 构造给定范围内的均匀分布采样;这些函数可能会预先做一些额外的工作,以便更快地对多个值进行采样。必须特别注意:确保四舍五入不会导致采样值小于(<)低位或者大于等于(>=)高位
  • 均匀分布:在概率论和统计学中,均匀分布也叫矩形分布,它是对称概率分布,在相同长度间隔的分布概率是可能相等的。

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    
    println!("  整数:   {}", rng.gen_range(0, 10)); // 半开放范围取随机值,即包括`低位`而不包括`高位`
    println!("  浮点数: {}", rng.gen_range(0.0, 10.0));
}

代码第 1 行,我们使用 use 关键字将 rand::Rng 引入作用域。rand::Rng 是在 RngCore trait 上自动实现的扩展 trait,它实现了高层次的泛型方法。

代码第 4 行,由系统创建本地线程,作用为延迟初始化的随机数生成器。

代码第 6,7 行,分别从半开放范围取整型(0,10)和浮点型(0.0,10.0)随机值,即包括低位而不包括高位

构建并运行后,结果大抵如图 3.1-2 所示,但具体值和笔者运行结果不一定相同。

rand-range-10

图 3.1-2

使用 Uniform 模块可以得到均匀分布的值。下述代码和上述代码具有相同的效果,但在相同范围内重复生成数字时,下述代码性能可能会更好。

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand::distributions::{Distribution, Uniform};

fn main() {
    let mut rng = rand::thread_rng();
    let die = Uniform::from(1..7);

    loop {
        let throw = die.sample(&mut rng);

        println!("  丢一次骰子: {}", throw);
        
        if throw == 6 {
            break;
        }
    }
}

本实例模拟丢骰子的程序。

代码第 1 行,我们使用 use 关键字将均匀分布模块 rand::distributions::{Distribution, Uniform}; 引入作用域。

其中 Distribution trait 必须引入,其将自动实现随机值的取值方法。若不引入,代码第 8 行的 sample 方法将不能使用。如图 3.1-3 所示。

rand-range-uniform-error

图 3.1-3

代码第 4 行,由系统创建本地线程,作用为延迟初始化的随机数生成器。

代码第 5 行,制定了均匀分布取随机值的半开放范围(1,7),包括 1 而不包括 7

代码第 8 行,使用 Distribution trait 实现的 sample 方法进行随机值的半开放范围取值。

构建并运行后,结果大抵如图 3.1-4 所示,但具体值和笔者运行结果不一定相同。

rand-range-uniform

图 3.1-4

生成给定分布随机数

rand_distr-badge cat-science-badge

默认情况下,随机数在 rand crate 中是均匀分布rand_distr crate 提供其它的分布类型。如要使用,首先实例化一个分布,然后在随机数生成器 rand::Rng 的帮助下,使用 Distribution::sample 从该分布中进行采样。

  • 均匀分布:在概率论和统计学中,均匀分布也叫矩形分布,它是对称概率分布,在相同长度间隔的分布概率是可能相等的。
  • rand_distr:rand_distr crate 是 rand::distributions 模块的一个超级集合,实现了诸多概率分布类型,如均匀分布、正态分布(Normal distribution)、柯西分布(Cauchy distribution)等。请参考本章 “crate 介绍”一节。
  • Distribution::sample:此函数创建一个迭代器,用来生成泛型 T 的随机值,其使用 rng 作为随机来源。

关于更多信息,可阅读相关可用分布文档。如下是一个使用正态(Normal)分布的实例。

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand_distr::{Distribution, Normal, NormalError};
use rand::thread_rng;

fn main() -> Result<(), NormalError> {
    let mut rng = thread_rng();
    let normal = Normal::new(2.0, 9.0)?; // 正态分布
    let v = normal.sample(&mut rng);

    println!("  正态分布: {}", v);

    Ok(())
}

代码第 1-5 行,分别为使用 use 将正态分布相关模块引入作用域,以及由系统创建本地线程,作用为延迟初始化的随机数生成器。

代码第 6,7 行,建立正态分布模型,以及使用通过正态分布 rand_distr::{Distribution, Normal, NormalError} trait 实现的 sample 方法从半开放范围(2.0,9.0)取随机值。

构建并运行后,结果大抵如图 3.1-5 所示,但具体值和笔者运行结果不一定相同。

rand-dist

图 3.1-5

生成自定义类型随机值

rand-badge cat-science-badge

随机生成一个元组 (i32, bool, f64) 和用户定义类型为 Point 的变量。为 Standard 实现 Distribution trait,以允许随机生成。

  • Standard:通用的随机值分布结构体,为 rand crate 中的众多基本类型实现。通常生成均匀分布的数值,并且具有与类型相适应的范围。
  • Distribution:创建概率分布类型的 trait,可以用来创建泛型 T 的随机实例的类型。提供 sample_iter 方法,该方法生成一个迭代器,从分布中采样。

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand::Rng;
use rand::distributions::{Distribution, Standard};

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl Distribution<Point> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
        let (rand_x, rand_y) = rng.gen();
        Point {
            x: rand_x,
            y: rand_y,
        }
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let rand_tuple = rng.gen::<(i32, bool, f64)>();
    let rand_point: Point = rng.gen();

    println!("  随机值元组:   {:?}", rand_tuple);
    println!("  随机值结构体: {:?}", rand_point);
}

这段代码行数虽多,但逻辑不复杂。

代码第 1,2 行,使用 use 将相关模块引入作用域。

代码第 5-8 行,自定义一个结构体类型 Point

最主要的是代码第 10-18 行,为 Standard 实现 Distribution trait,其中 Distribution trait 的类型为结构体 Point。这样可以在 sample 方法中,直接将产生的随机值封装到结构体 Point

构建并运行后,结果大抵如图 3.1-6 所示,但具体值和笔者运行结果不一定相同。

rand-custom

图 3.1-6

从一组字母数字字符创建随机密码

rand-badge cat-os-badge

随机生成一个给定长度的 ASCII 字符串,范围为 A-Z,a-z,0-9,使用字母数字样本。

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .collect();

    println!("  随机密码: {}", rand_string);
}

代码第 1,2 行使用 use 关键字将相关模块引入作用域,其中 rand::distributions::Alphanumeric字母数字样本,范围为 A-Z,a-z,0-9

代码第 6,7 行使用 sample_iter 方法迭代从字母数字样本产生总长度为 30 的随机密码。

构建并运行后,结果大抵如图 3.1-7 所示,但具体值和笔者运行结果不一定相同。

rand-passwd

图 3.1-7

从一组用户定义字符创建随机密码

rand-badge cat-os-badge

使用用户自定义的字节字符串,使用 gen_range 函数,随机生成一个给定长度的 ASCII 字符串。

  • gen_range:此函数在 [低位,高位] 范围内生成一个随机值。此范围为半开放范围,即包括低位而不包括高位。此函数针对给定范围内仅生成一个样本值的情况进行了优化。另外,此函数为均匀分布类型,如果从相同的范围重复抽样取值,执行速度将会更快。

以下实例代码引用自 rust-cookbook 项目,笔者在其基础上稍作修改。

use rand::Rng;

fn main() {
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                            abcdefghijklmnopqrstuvwxyz\
                            0123456789)(*&^%$#@!~";
    const PASSWORD_LEN: usize = 30;

    let mut rng = rand::thread_rng(); // 由系统创建延迟初始化的随机数生成器本地线程

    let password: String = (0..PASSWORD_LEN)
        .map(|_| {
            let idx = rng.gen_range(0, CHARSET.len()); // 半开放范围取随机值,即包括`低位`而不包括`高位`
            CHARSET[idx] as char
        })
        .collect();

    println!("  随机密码: {:?}", password);
}

代码第 1 行,我们使用 use 关键字将 rand::Rng 引入作用域。rand::Rng 是在 RngCore trait 上自动实现的扩展 trait,它实现了高层次的泛型方法。

代码第 3,4 行,自定义字节字符串样本,并设定密码长度。

代码第 11 行,通过 0..PASSWORD_LEN 匹配一个闭区间范围,其是密码字符串的长度。

代码第 12-15 行,首先在 map 方法内,通过闭包从自定义字节字符串样本中取随机值,其将根据密码字符串的长度迭代产生。然后通过 collect 方法绑定到变量 password

构建并运行后,结果大抵如图 3.1-8 所示,但具体值和笔者运行结果不一定相同。

rand-choose

图 3.1-8