1. 数学
1.1 线性代数
  需要安装ndarray库,可通过cargo add ndarray 命令安装
| [dependencies]ndarray = "0.15.6"
 
 | 
1.1.1 矩阵相加
  使用 ndarray::arr2 创建两个二维(2-D)矩阵,并按元素方式求和。注意:sum 的计算方式为 let sum = &a + &b,借用 & 运算符获得 a 和 b 的引用,可避免销毁他们,使它们可以稍后显示。这样,就创建了一个包含其和的新数组。
| use ndarray::arr2;
 fn main() {
 let a = arr2(&[[1, 2, 3], [4, 5, 6]]);
 
 let b = arr2(&[[6, 5, 4], [3, 2, 1]]);
 
 let sum = &a + &b;
 
 println!("{}", a);
 println!("+");
 println!("{}", b);
 println!("=");
 println!("{}", sum);
 }
 
 | 
| [[1, 2, 3],[4, 5, 6]]
 +
 [[6, 5, 4],
 [3, 2, 1]]
 =
 [[7, 7, 7],
 [7, 7, 7]]
 
 | 
1.1.2 矩阵相乘
  使用 ndarray::arr2 创建两个矩阵,并使用 ndarray::ArrayBase::dot 对它们执行矩阵乘法。
| use ndarray::arr2;
 fn main() {
 let a = arr2(&[[1, 2, 3], [4, 5, 6]]);
 
 let b = arr2(&[[6, 3], [5, 2], [4, 1]]);
 
 println!("{}", a.dot(&b));
 }
 
 | 
1.1.3 标量、向量Vector、矩阵相乘
  使用 ndarray::arr1 创建一维(1-D)数组或向量(vector),使用 ndarray::arr2 创建二维(2-D)数组(矩阵)。首先,一个标量乘以一个 vector 得到另一个 vector。然后,使用 ndarray::Array2::dot 将矩阵乘以新的 vector(矩阵相乘使用 dot 函数,而 * 运算符执行元素方式的乘法)。在 ndarray crate 中,根据上下文,一维数组可以解释为行 vector 或列 vector。如果 vector 表示的方向很重要,则必须使用只有一行或一列的二维(2-D)数组。在本实例中,vector 是右侧的一维(1-D)数组,因此 dot 函数将其处理为列 vector。
| use ndarray::{arr1, arr2, Array1};
 fn main() {
 let scalar = 4;
 
 let vector = arr1(&[1, 2, 3]);
 
 let matrix = arr2(&[[4, 5, 6],
 [7, 8, 9]]);
 
 let new_vector: Array1<_> = scalar * vector;
 println!("{}", new_vector);
 
 let new_matrix = matrix.dot(&new_vector);
 println!("{}", new_matrix);
 }
 
 | 
1.1.4 向量Vector比较
  需要调整ndarray库和approx库,可通过cargo add ndarray --features approx 命令重新安装及安装cargo add approx
| approx = "0.4" ndarray = { version = "0.15.6", features = ["approx"] }
 
 | 
  ndarray crate 支持多种创建数组的方法——此实例使用 from 从 std::Vec 创建数组 ndarray::Array。然后,对数组以元素方式求和。下面的实例按元素方式比较两个浮点型 vector。浮点数的存储通常不精确,因此很难进行精确的比较。但是,approx crate 中的 assert_abs_diff_eq! 宏允许方便地比较浮点型元素。要将 approx 和 ndarray 两个 crate一起使用,必须在 Cargo.toml 文件中的 ndarray 依赖项添加 approx 特性。例如:ndarray = { version = "0.13", features = ["approx"] }。此实例还包含其他所有权示例。在这里,let z = a + b 执行后,会销毁a and b,然后所有权会转移到 z。或者,let w = &c + &d 创建一个新的 vector,而不销毁 c 或者 d,允许以后对它们进行修改。有关其他详细信息,请参见带有两个数组的二进制运算符。
| use ndarray::Array;use approx::assert_abs_diff_eq;
 
 fn main() {
 let a = Array::from(vec![1., 2., 3., 4., 5.]);
 let b = Array::from(vec![5., 4., 3., 2., 1.]);
 let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
 let mut d = Array::from(vec![5., 4., 3., 2., 1.]);
 
 let z = a + b;
 let w = &c + &d;
 
 assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));
 
 println!("c = {}", c);
 c[0] = 10.;
 d[1] = 10.;
 
 assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
 }
 
 | 
1.1.5 向量Vector范数
  范数,是具有“长度”概念的函数。在线性代数、泛函分析及相关的数学领域,范数是一个函数,是矢量空间内的所有矢量赋予非零的正长度或大小。半范数可以为非零的矢量赋予零长度。
  示例展示了 Array1 类型、ArrayView1 类型、fold 方法,以及 dot 方法在计算给定 vector 的 l1 和 l2 范数时的用法。 l2_norm 函数是两者中较简单的,它计算一个 vector 与自身的点积(dot product,数量积)的平方根。 l1_norm 函数通过 fold 运算来计算元素的绝对值(也可以通过 x.mapv(f64::abs).scalar_sum() 执行,但是会为 mapv 的结果分配一个新的数组)。请注意:l1_norm 和 l2_norm 都采用 ArrayView1 类型。这个实例考虑了 vector 范数,所以范数函数只需要接受一维视图(ArrayView1)。虽然函数可以使用类型为 &Array1<f64> 的参数,但这将要求调用方引用拥有所有权的数组,这比访问视图更为严格(因为视图可以从任意数组或视图创建,而不仅仅是从拥有所有权的数组创建)。Array 和 ArrayView 都是 ArrayBase 的类型别名。于是,大多数的调用方参数类型可以是 &ArrayBase<S, Ix1> where S: Data,这样调用方就可以使用 &array 或者 &view 而不是 x.view()。如果该函数是公共 API 的一部分,那么对于用户来说,这可能是一个更好的选择。对于内部函数,更简明的 ArrayView1<f64> 或许更合适。
| use ndarray::{array, Array1, ArrayView1};
 fn l1_norm(x: ArrayView1<f64>) -> f64 {
 x.fold(0., |acc, elem| acc + elem.abs())
 }
 
 fn l2_norm(x: ArrayView1<f64>) -> f64 {
 x.dot(&x).sqrt()
 }
 
 fn normalize(mut x: Array1<f64>) -> Array1<f64> {
 let norm = l2_norm(x.view());
 x.mapv_inplace(|e| e / norm);
 x
 }
 
 fn main() {
 let x = array![1., 2., 3., 4., 5.];
 println!("||x||_2 = {}", l2_norm(x.view()));
 println!("||x||_1 = {}", l1_norm(x.view()));
 println!("标准化 x 收益率 {:?}", normalize(x));
 }
 
 | 
| ||x||_2 = 7.416198487095663||x||_1 = 15
 标准化 x 收益率 [0.13483997249264842, 0.26967994498529685, 0.40451991747794525, 0.5393598899705937, 0.674199862463242], shape=[5], strides=[1], layout=CFcf (0xf), const ndim=1
 
 | 
1.1.6 矩阵求逆
  需要安装nalgebra库,可通过cargo add nalgebra 命令安装
  用 nalgebra::Matrix3 创建一个 3x3 的矩阵,如果可能的话,将其求逆。
| use nalgebra::Matrix3;
 fn main() {
 let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
 println!("矩阵m1 = {}", m1);
 match m1.try_inverse() {
 Some(inv) => {
 println!("m1 的逆矩阵是: {}", inv);
 }
 None => {
 println!("m1不可逆!");
 }
 }
 }
 
 | 
| 矩阵m1 = ┌       ┐
 │ 2 1 1 │
 │ 3 2 1 │
 │ 2 1 2 │
 └       ┘
 
 
 m1 的逆矩阵是:
 ┌          ┐
 │  3 -1 -1 │
 │ -4  2  1 │
 │ -1  0  1 │
 └          ┘
 
 | 
1.1.7 (反)序列化矩阵
  实例实现将矩阵序列化为 JSON,以及从 JSON 反序列化出矩阵。序列化由 serde_json::to_string 处理,serde_json::from_str 则执行反序列化。请注意:序列化后再反序列化将返回原始矩阵。
  需要调整nalgebra和安装serde_json库,可通过cargo add nalgebra --features serde-serialize添加序列化特征,cargo add serde_json 命令安装
| nalgebra = { version = "0.32.2", features = ["serde-serialize"] }serde_json = "1.0.96"
 
 | 
| extern crate nalgebra;extern crate serde_json;
 
 use nalgebra::DMatrix;
 
 fn main() -> Result<(), std::io::Error> {
 let row_slice: Vec<i32> = (1..5001).collect();
 let matrix = DMatrix::from_row_slice(50, 100, &row_slice);
 
 
 let serialized_matrix = serde_json::to_string(&matrix)?;
 
 
 let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?;
 
 
 assert!(deserialized_matrix == matrix);
 
 Ok(())
 }
 
 | 
1.2 三角学
1.2.1 计算三角形的边长
  计算直角三角形斜边的长度,其中斜边的角度为 2 弧度,对边长度为 80。
| fn main() {let angle: f64 = 2.0;
 let side_length = 80.0;
 
 let hypotenuse = side_length / angle.sin();
 
 println!("斜边: {}", hypotenuse);
 }
 
 | 
1.2.2 验证正切(tan)等于正弦(sin)除以余弦(cos)
  验证 tan(x) 是否等于 sin(x)/cos(x),其中 x=6。
| fn main() {let x: f64 = 6.0;
 
 let a = x.tan();
 let b = x.sin() / x.cos();
 
 assert_eq!(a, b);
 }
 
 | 
1.2.3 地球上两点之间的距离
  实例使用半正矢公式计算地球上两点之间的距离(以公里为单位)。两个点用一对经纬度表示,然后,to_radians 将它们转换为弧度。sin、cos、powi 以及 sqrt 计算中心角。最终,可以计算出距离。
| fn main() {let earth_radius_kilometer = 6371.0_f64;
 let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64);
 let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);
 
 let paris_latitude = paris_latitude_degrees.to_radians();
 let london_latitude = london_latitude_degrees.to_radians();
 
 let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians();
 let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians();
 
 let central_angle_inner = (delta_latitude / 2.0).sin().powi(2)
 + paris_latitude.cos() * london_latitude.cos() * (delta_longitude / 2.0).sin().powi(2);
 let central_angle = 2.0 * central_angle_inner.sqrt().asin();
 
 let distance = earth_radius_kilometer * central_angle;
 
 println!("地球表面巴黎和伦敦之间的距离是 {:.1} 公里", distance);
 }
 
 | 
1.3 复数
  需要安装num库,可通过cargo add num 命令安装
| [dependencies]num = "0.4.0"
 
 | 
1.3.1 创建复数
  创建类型num::complex::Complex 的复数,复数的实部和虚部必须是同一类型
| fn main() {let complex_integer = num::complex::Complex::new(10, 20);
 let complex_float = num::complex::Complex::new(10.1, 20.1);
 
 println!("复整数: {}", complex_integer);
 println!("复浮点数: {}", complex_float);
 }
 
 | 
| 复整数: 10+20i复浮点数: 10.1+20.1i
 
 | 
1.3.2 复数相加
  对复数执行数学运算与对内置类型执行数学运算是一样的:计算的数字必须是相同的类型(如浮点数或整数)
| fn main() {let complex_num1 = num::complex::Complex::new(10.0, 20.0);
 let complex_num2 = num::complex::Complex::new(3.1, -4.2);
 
 let sum = complex_num1 + complex_num2;
 
 println!("和: {}", sum);
 }
 
 | 
1.3.3 复数的数学函数
  对复数执行数学运算与对内置类型执行数学运算是一样的:计算的数字必须是相同的类型(如浮点数或整数)
| use num::complex::Complex;use std::f64::consts::PI;
 
 fn main() {
 let x = Complex::new(0.0, 2.0 * PI);
 
 println!("e^(2i * pi) = {}", x.exp());
 }
 
 | 
| e^(2i * pi) = 1-0.00000000000000024492935982947064i 
 | 
1.4 统计学
1.4.1 集中趋势度量
  本节实例计算 Rust 数组中包含的数据集的集中趋势度量。对于一个空的数据集,可能没有平均数、中位数或众数去计算,因此每个函数都返回 [Option] ,由调用者处理。
- 实例1: 是通过对数据引用生成一个迭代器,然后计算平均数(所有测量值的总和除以测量值的计数),并使用 [sum]和[len]函数分别确定值的总和及值的计数。
| fn main() {let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
 
 let sum = data.iter().sum::<i32>() as f32;
 let count = data.len();
 
 let mean = match count {
 positive if positive > 0 => Some(sum / count as f32),
 _ => None,
 };
 
 println!("数据的平均值是 {:?}", mean);
 }
 
 | 
- 实例2: 使用快速选择算法(Quick Select Algorithm)计算中位数,该算法只对已知可能包含中位数的数据集的分区进行排序,从而避免了完整[排序][sort]。该算法使用[cmp]和[Ordering]简便地地决定要检查的下一个分区,并使用[split_at]为每个步骤的下一个分区选择一个任意的枢轴量。
| use std::cmp::Ordering;
 fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> {
 match data.len() {
 0 => None,
 _ => {
 let (pivot_slice, tail) = data.split_at(1);
 let pivot = pivot_slice[0];
 let (left, right) = tail.iter().fold((vec![], vec![]), |mut splits, next| {
 {
 let (ref mut left, ref mut right) = &mut splits;
 if next < &pivot {
 left.push(*next);
 } else {
 right.push(*next);
 }
 }
 splits
 });
 
 Some((left, pivot, right))
 }
 }
 }
 
 fn select(data: &[i32], k: usize) -> Option<i32> {
 let part = partition(data);
 
 match part {
 None => None,
 Some((left, pivot, right)) => {
 let pivot_idx = left.len();
 
 match pivot_idx.cmp(&k) {
 Ordering::Equal => Some(pivot),
 Ordering::Greater => select(&left, k),
 Ordering::Less => select(&right, k - (pivot_idx + 1)),
 }
 }
 }
 }
 
 fn median(data: &[i32]) -> Option<f32> {
 let size = data.len();
 
 match size {
 even if even % 2 == 0 => {
 let fst_med = select(data, (even / 2) - 1);
 let snd_med = select(data, even / 2);
 
 match (fst_med, snd_med) {
 (Some(fst), Some(snd)) => Some((fst + snd) as f32 / 2.0),
 _ => None,
 }
 }
 odd => select(data, odd / 2).map(|x| x as f32),
 }
 }
 
 fn main() {
 let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
 
 let part = partition(&data);
 println!("分区是 {:?}", part);
 
 let sel = select(&data, 5);
 println!("有序索引 {} 处的选择是 {:?}", 5, sel);
 
 let med = median(&data);
 println!("中位数是 {:?}", med);
 }
 
 | 
| 分区是 Some(([1, 1, 1], 3, [6, 5, 8, 8, 10, 11]))有序索引 5 处的选择是 Some(6)
 中位数是 Some(5.5)
 
 | 
- 实例3: 使用可变的 [HashMap]来计算众数,[fold]和[entry]API 用来从集合中收集每个不同整数的计数。[HashMap]中最常见的值可以用[max_by_key]取得。
| use std::collections::HashMap;
 fn main() {
 let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
 
 let frequencies = data.iter().fold(HashMap::new(), |mut freqs, value| {
 *freqs.entry(value).or_insert(0) += 1;
 freqs
 });
 let mode = frequencies
 .into_iter()
 .max_by_key(|&(_, count)| count)
 .map(|(value, _)| *value);
 
 println!("众数是 {:?}", mode);
 }
 
 | 
1.4.2 计算标准偏差
  计算一组测量值的标准偏差和 z 分数(z-score)。标准偏差定义为方差的平方根(用 f32 浮点型的 [sqrt] 计算),其中方差是每个测量值与平均数之间的平方差的和除以测量次数。z分数(z-score)是指单个测量值偏离数据集平均数的标准差数,z = (x - μ) / σ,其中 x 是数据点的值,μ 是数据集的平均值,σ 是数据集的标准差。。
| fn mean(data: &[i32]) -> Option<f32> {let sum = data.iter().sum::<i32>() as f32;
 let count = data.len();
 
 match count {
 positive if positive > 0 => Some(sum / count as f32),
 _ => None,
 }
 }
 
 fn std_deviation(data: &[i32]) -> Option<f32> {
 match (mean(data), data.len()) {
 (Some(data_mean), count) if count > 0 => {
 let variance = data
 .iter()
 .map(|value| {
 let diff = data_mean - (*value as f32);
 
 diff * diff
 })
 .sum::<f32>()
 / count as f32;
 
 Some(variance.sqrt())
 }
 _ => None,
 }
 }
 
 fn main() {
 let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
 
 let data_mean = mean(&data);
 println!("平均数是 {:?}", data_mean);
 
 let data_std_deviation = std_deviation(&data);
 println!("标准差是 {:?}", data_std_deviation);
 
 let zscore = match (data_mean, data_std_deviation) {
 (Some(mean), Some(std_deviation)) => {
 let diff = data[4] as f32 - mean;
 
 Some(diff / std_deviation)
 }
 _ => None,
 };
 println!("索引 4 处数据的 Z 分数(值为 {})为 {:?}", data[4], zscore);
 }
 
 | 
| 平均数是 Some(5.4)标准差是 Some(3.6110942)
 索引 4 处数据的 Z 分数(值为 5)为 Some(-0.11076978)
 
 | 
1.5 其它数学计算
1.5.1 大数
  BigInt 使得超过 128 位的大整数计算成为可能。
| use num::bigint::{BigInt, ToBigInt};
 fn factorial(x: i32) -> BigInt {
 if let Some(mut factorial) = 1.to_bigint() {
 for i in 1..(x + 1) {
 factorial = factorial * i;
 }
 factorial
 } else {
 panic!("计算阶乘失败!");
 }
 }
 
 fn main() {
 println!("{}! 等于 {}", 100, factorial(100));
 }
 
 | 
| 100! 等于 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
 |