1. 数学

1.1 线性代数

  需要安装ndarray库,可通过cargo add ndarray 命令安装

[dependencies]
ndarray = "0.15.6"

1.1.1 矩阵相加

  使用 ndarray::arr2 创建两个二维(2-D)矩阵,并按元素方式求和。注意:sum 的计算方式为 let sum = &a + &b,借用 & 运算符获得 ab 的引用,可避免销毁他们,使它们可以稍后显示。这样,就创建了一个包含其和的新数组。

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);
}
  • 运行cargo run输出
[[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));
}
  • 运行cargo run输出
[[28, 10], # 28 = a[1][1] * b[1][1] + a[1][2] * b[2][1] + a[1][3] * b[3][1]
[73, 28]]

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);
}
  • 运行cargo run输出
[4, 8, 12] # new_vector 列
[128, 200] # new_matrix 列

1.1.4 向量Vector比较

  需要调整ndarray库和approx库,可通过cargo add ndarray --features approx 命令重新安装及安装cargo add approx

approx = "0.4" # 代码目前在0.4版本测试通过,最新0.5.1上测试未通过
ndarray = { version = "0.15.6", features = ["approx"] }

  ndarray crate 支持多种创建数组的方法——此实例使用 fromstd::Vec 创建数组 ndarray::Array。然后,对数组以元素方式求和。下面的实例按元素方式比较两个浮点型 vector。浮点数的存储通常不精确,因此很难进行精确的比较。但是,approx crate 中的 assert_abs_diff_eq! 宏允许方便地比较浮点型元素。要将 approxndarray 两个 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.]));
}
  • 运行cargo run输出
[4, 8, 12] # new_vector 列
[128, 200] # new_matrix 列

1.1.5 向量Vector范数

  范数,是具有“长度”概念的函数。在线性代数、泛函分析及相关的数学领域,范数是一个函数,是矢量空间内的所有矢量赋予非零的正长度或大小。半范数可以为非零的矢量赋予零长度。
  示例展示了 Array1 类型、ArrayView1 类型、fold 方法,以及 dot 方法在计算给定 vectorl1l2 范数时的用法。 l2_norm 函数是两者中较简单的,它计算一个 vector 与自身的点积(dot product,数量积)的平方根。 l1_norm 函数通过 fold 运算来计算元素的绝对值(也可以通过 x.mapv(f64::abs).scalar_sum() 执行,但是会为 mapv 的结果分配一个新的数组)。请注意:l1_norml2_norm 都采用 ArrayView1 类型。这个实例考虑了 vector 范数,所以范数函数只需要接受一维视图(ArrayView1)。虽然函数可以使用类型为 &Array1<f64> 的参数,但这将要求调用方引用拥有所有权的数组,这比访问视图更为严格(因为视图可以从任意数组或视图创建,而不仅仅是从拥有所有权的数组创建)。ArrayArrayView 都是 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));
}
  • 运行cargo run输出
||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 = "0.32.2"

  用 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不可逆!");
}
}
}
  • 运行cargo run输出
矩阵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)?;

// 验证反序列化出的矩阵 `deserialized_matrix` 等同于原始矩阵 `matrix`
assert!(deserialized_matrix == matrix);

Ok(())
}
  • 运行cargo run校验

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);
}
  • 运行cargo run输出
斜边: 87.98001362356932

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);
}
  • 运行cargo run校验

1.2.3 地球上两点之间的距离

  实例使用半正矢公式计算地球上两点之间的距离(以公里为单位)。两个点用一对经纬度表示,然后,to_radians 将它们转换为弧度。sincospowi 以及 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);
}
  • 运行cargo run输出
地球表面巴黎和伦敦之间的距离是 335.0 公里

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);
}
  • 运行cargo run输出
复整数: 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);
}
  • 运行cargo run输出
和: 13.1+15.8i

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()); // = -1
}
  • 运行cargo run输出
e^(2i * pi) = 1-0.00000000000000024492935982947064i # 输出不是精确的 1 而是带有一个小的虚部,这是由于浮点数的有限精度造成的。

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);
}
  • 运行cargo run输出
数据的平均值是 Some(5.4)
  • 实例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);
}
  • 运行cargo run输出
分区是 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);
}
  • 运行cargo run输出
众数是 Some(1)

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);
}
  • 运行cargo run输出
平均数是 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)); // 约为 9.332622e+157
}
  • 运行cargo run输出
100! 等于 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000