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
|