#slice #elements #mut #spaced #memory #forms #immediately

strided

步进切片。该库提供了两种类型 StridedMutStrided,分别作为 &[T]&mut [T] 的一般化形式,其中元素在内存中按规律分布,但不必是相邻的。

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 模式

Download history 8/week @ 2023-12-18 8/week @ 2023-12-25 35/week @ 2024-01-01 47/week @ 2024-01-08 14/week @ 2024-01-15 51/week @ 2024-01-22 30/week @ 2024-01-29 91/week @ 2024-02-05 80/week @ 2024-02-12 110/week @ 2024-02-19 67/week @ 2024-02-26 68/week @ 2024-03-04 68/week @ 2024-03-11 112/week @ 2024-03-18 93/week @ 2024-03-25 133/week @ 2024-04-01

413 次每月下载
用于 5 遗留物(4 个直接使用)

MIT/Apache

47KB
841

步进切片。

Build Status Coverage Status

该库提供了两种类型 StrideMutStride,分别作为 &[T]&mut [T] 的一般化形式,其中元素在内存中按规律分布,但不必是相邻的。

文档, crates.io


lib.rs:

步进切片。

该库提供了两种类型 StrideMutStride,分别作为 &[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),它允许返回一个元组而不是迭代器,以便直接解构。继续使用上述值,leftright 分别指向其父切片的交替元素,起始索引分别为 01

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的使用,这允许startend用于递归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不需要是一个斜切切片,因为它永远不会被分割成交替的元素。)

无运行时依赖项