11 个版本
使用旧的 Rust 2015
0.2.9 | 2015年5月24日 |
---|---|
0.2.8 | 2015年4月3日 |
0.2.7 | 2015年2月23日 |
0.2.4 | 2015年1月9日 |
0.1.0 | 2014年11月16日 |
#2811 in Rust 模式
413 次每月下载
用于 5 个 遗留物(4 个直接使用)
47KB
841 行
步进切片。
该库提供了两种类型 Stride
和 MutStride
,分别作为 &[T]
和 &mut [T]
的一般化形式,其中元素在内存中按规律分布,但不必是相邻的。
lib.rs
:
步进切片。
该库提供了两种类型 Stride
和 MutStride
,分别作为 &[T]
和 &mut [T]
的一般化形式,其中元素在内存中按规律分布,但不必是相邻的。
例如,给定一个基本数组 [1, 2, 3, 4, 5]
,元素 [1, 3, 5]
是步长为 2 的步长切片,而 [1, 4]
的步长为 3。任何切片都可以视为步长为 1 的步长切片。
这提供了一种功能,通过它可以安全有效地操作切片中每个 n
个元素(即使是可变的),尽可能接近常规切片。这使人们不必担心步长管理、&mut
或任何 unsafe
代码。
快速入门
主要的函数是 .substrides(n)
和 .substrides_mut(n)
,它们返回一个遍历一系列 n
个新步长切片(共享和可变,分别)的迭代器,每个切片都指向每个 n
个元素,并且每个切片从下一个连续偏移量开始。例如,下面的代码中 n = 3
。
use strided::MutStride;
let mut v = [1u8, 2, 3, 4, 5];
let mut all = MutStride::new(&mut v);
let mut substrides = all.substrides_mut(3);
let a = substrides.next().unwrap();
let b = substrides.next().unwrap();
let c = substrides.next().unwrap();
assert!(substrides.next().is_none()); // there was exactly 3.
assert_eq!(a, MutStride::new(&mut [1, 4]));
assert_eq!(b, MutStride::new(&mut [2, 5]));
assert_eq!(c, MutStride::new(&mut [3]));
当 n = 2
时,有一个缩写 substrides2
(分别 substrides2_mut
),它允许返回一个元组而不是迭代器,以便直接解构。继续使用上述值,left
和 right
分别指向其父切片的交替元素,起始索引分别为 0
和 1
。
let (left, right) = all.substrides2_mut();
assert_eq!(left, MutStride::new(&mut [1, 3, 5]));
assert_eq!(right, MutStride::new(&mut [2, 4]));
许多常规切片功能都可用,例如索引、迭代器和切片。
let (mut left, right) = all.substrides2_mut();
assert_eq!(left[2], 5);
assert!(right.get(10).is_none()); // out of bounds
left[2] += 10;
match left.get_mut(0) {
Some(val) => *val -= 1,
None => {}
}
assert_eq!(right.iter().fold(0, |sum, a| sum + *a), 2 + 4);
for val in left.iter_mut() {
*val /= 2
}
所有权和 reborrow
MutStride
有一个方法 reborrow
,其签名如下:
impl<'a, T> MutStride<'a, T> {
pub fn reborrow<'b>(&'b mut self) -> MutStride<'b, T> { ... }
}
这意味着它允许临时将步长切片视为具有更短生命周期的切片。这个方法非常重要,因为 MutStride
上的许多方法都通过值传递 self
并消耗所有权...如果想要多次使用步长切片,这会相当不幸。
reborrow
返回的临时对象可以与消耗方法一起使用,这允许父切片在临时对象消失后继续使用。例如,MutStride
上的所有分割和切片方法都消耗所有权,因此在这种情况下,需要 reborrow
才能继续使用 left
。
let (mut left, right) = all.substrides2_mut();
assert_eq!(left.reborrow().slice_mut(1, 3), MutStride::new(&mut [3, 5]));
assert_eq!(left.reborrow().slice_from_mut(2), MutStride::new(&mut [5]));
assert_eq!(left.reborrow().slice_to_mut(2), MutStride::new(&mut [1, 3]));
// no reborrow:
assert_eq!(right.split_at_mut(1),
(MutStride::new(&mut [2]), MutStride::new(&mut [4])));
// println!("{}", right); // error: use of moved value `right`.
这些扭曲是必要的,以确保&mut
不能发生别名,同时仍然保持灵活性:保留具有最大可能生命周期(即它们所在的非斜切切片的生命周期)。从理论上讲,在&mut []
情况下也是必要的,但是编译器会插入隐式的重新借用,因此很少需要手动执行。
在实践中,只有在编译器抱怨使用了移动值时,才需要插入reborrow
。
共享的Stride
相当于&[]
,并且仅处理&
引用,使得所有权转移和reborrow
变得不必要,因此它的所有方法都与&[]
上的方法相同。
示例
快速傅里叶变换(FFT)是一种信号处理算法,在O(n log n)
时间内执行长度为n
的离散傅里叶变换(DFT)。DFT将波形分解为正弦波和余弦波的叠加,由于傅里叶变换的某些良好性质,它是许多其他算法的重要组成部分。
第一种FFT算法是Cooley-Tukey算法。时间抽取变体通过计算等长度子数组的FFT并将这些结果组合起来来实现。这种间隔正好与这个库提供的斜切相匹配,因此可以使用这种库以非常自然的方式创建FFT算法。
下面是基数2情况的实现,即当长度n
是2的幂时。在这种情况下,只需要两个斜切子数组:正好是substrides2
提供的交替子数组。注意reborrow
的使用,这允许start
和end
用于递归fft
调用,然后在循环中再次使用。
extern crate strided;
extern crate num; // https://github.com/rust-lang/num
use std::f64;
use num::complex::{Complex, Complex64};
use strided::{MutStride, Stride};
/// Writes the forward DFT of `input` to `output`.
fn fft(input: Stride<Complex64>, mut output: MutStride<Complex64>) {
// check it's a power of two.
assert!(input.len() == output.len() && input.len().count_ones() == 1);
// base case: the DFT of a single element is itself.
if input.len() == 1 {
output[0] = input[0];
return
}
// split the input into two arrays of alternating elements ("decimate in time")
let (evens, odds) = input.substrides2();
// break the output into two halves (front and back, not alternating)
let (mut start, mut end) = output.split_at_mut(input.len() / 2);
// recursively perform two FFTs on alternating elements of the input, writing the
// results into the first and second half of the output array respectively.
fft(evens, start.reborrow());
fft(odds, end.reborrow());
// exp(-2πi/N)
let twiddle = Complex::from_polar(&1.0, &(-2.0 * f64::consts::PI / input.len() as f64));
let mut factor = Complex::new(1., 0.);
// combine the subFFTs with the relations:
// X_k = E_k + exp(-2πki/N) * O_k
// X_{k+N/2} = E_k - exp(-2πki/N) * O_k
for (even, odd) in start.iter_mut().zip(end.iter_mut()) {
let twiddled = factor * *odd;
let e = *even;
*even = e + twiddled;
*odd = e - twiddled;
factor = factor * twiddle;
}
}
fn main() {
let a = [Complex::new(2., 0.), Complex::new(1., 0.),
Complex::new(2., 0.), Complex::new(1., 0.)];
let mut b = [Complex::new(0., 0.); 4];
fft(Stride::new(&a), MutStride::new(&mut b));
println!("forward: {:?} -> {:?}", &a, &b);
}
上述算法的复杂度为O(n log n)
,但它的常数因子比像FFTW这样的优化库要大得多。(严格来说,output
不需要是一个斜切切片,因为它永远不会被分割成交替的元素。)