首页 文章

如何避免重复从现有数组构造固定大小的数组?

提问于
浏览
1

从现有数组创建固定大小的数组时,读取和写入可能会很繁琐,例如:

let foo: [i32; 8] = [
    bar[1], bar[2], bar[3], bar[4],
    taz[1], taz[2], taz[3], taz[4],
];

除了使用 for 循环来分配值之外,Rust是否提供了一种写入此方法而无需手动扩展数组的方法?

例如,像Python's unpacking generalizations

let foo: [i32; 8] = [*bar[1..5], *taz[1..5]];

注意:现实世界的示例使用了更多的项目,只是为了保持示例的简短 .


使用向量这是可能的,但是从固定大小的数组移动到向量不是免费的(测试它并且它在释放模式中生成相当多的组件,它执行写入的转换,使用堆内存,其中仅使用简单版本需要堆栈内存) .

let foo: Vec<i32> = bar[1..5].iter().chain(taz[1..5].iter()).cloned().collect();

2 回答

  • 0

    为了好奇,这里是一个支持解包的数组连接宏(限于有限数量的大小,这个宏很小,可以扩展) .

    与任何其他解决方案(要了解的配置文件)相比,我没有任何关于此性能或零成本的要求 . 如果您一直使用固定大小的数组,则所有边界检查都在编译时进行 .

    第一个用法示例:

    fn main() {
        let data = [0, 1, 2, 3, 4, 5, 6, 7, 8];
    
        println!("{:?}", concat_arrays!([1, 2, 3] [4] [5, 6]));
        // [1, 2, 3, 4, 5, 6]
    
        println!("{:?}", concat_arrays!(data[3..];3 [-1] data;3));
        // [3, 4, 5, -1, 0, 1, 2]
    
        // let's look at the macro expansion of the last one
        println!("{}", concat_arrays!(@build stringify [] data[3..];3 [-1] data;3));
        // [ data[3..][0] , data[3..][1] , data[3..][2] , -1 , data[0] , data[1] , data[2] ]
    }
    

    然后执行:(playground link)

    /// Concatenate array literals and fixed-size unpacked parts of arrays and slices
    ///
    /// Usage: `concat_arrays!(fragments)`  
    /// where each fragment is either an array literal: `[x, y, z]`  
    /// or an expression and how many elements to unpack: `expression;N`  
    /// where `N` must be an integer literal
    ///
    /// See: https://gitlab.com/snippets/27095
    /// for a script to generate a macro supporting many more arguments.
    macro_rules! concat_arrays {
        // last step -> build an expression
        (@build as_expr [$($t:expr),*]) => {
            [$($t),*]
        };
        (@build $m:ident [$($t:expr),*]) => {
            $m!([$($t),*])
        };
        (@build $m:ident [$($t:expr),*] [$($e:expr),+] $($more:tt)*) => {
            concat_arrays!(@build $m [$($t,)* $($e),*] $($more)*)
        };
        (@build $m:ident [$($t:expr),*] $e:expr;1 $($more:tt)*) => {
            concat_arrays!(@build $m [$($t,)* $e[0]] $($more)*)
        };
        (@build $m:ident [$($t:expr),*] $e:expr;2 $($more:tt)*) => {
            concat_arrays!(@build $m [$($t,)* $e[0], $e[1]] $($more)*)
        };
        (@build $m:ident [$($t:expr),*] $e:expr;3 $($more:tt)*) => {
            concat_arrays!(@build $m [$($t,)* $e[0], $e[1], $e[2]] $($more)*)
        };
        (@build $m:ident [$($t:expr),*] $e:expr;4 $($more:tt)*) => {
            concat_arrays!(@build $m [$($t,)* $e[0], $e[1], $e[2], $e[3]] $($more)*)
        };
        (@build $m:ident [$($t:expr),*] $e:expr;5 $($more:tt)*) => {
            concat_arrays!(@build $m [$($t,)* $e[0], $e[1], $e[2], $e[3], $e[4]] $($more)*)
        };
    
        // user facing case
        ($($t:tt)+) => {
            concat_arrays!(@build as_expr [] $($t)+)
        }
    }
    

    应用于您的问题,我会这样做:

    concat_arrays!(bar[1..];4 taz[1..];4)

    这是简洁的,但肯定的是,它有一些问题,例如,在特定宏的特殊语法中,以及源自 4 必须是文字的问题,不仅仅是有限列表中的文字 .


    编辑:

    请参阅expanded macro,包括用于生成支持预定义数量的参数的脚本,该宏可以比此处给出的示例大得多 .

  • 2

    如果你不想进入不安全的代码,我会创建一个 mut Vec<T> ,然后使用 extend_from_slice() 逐步扩展它的切片:

    fn main() {
        let bar = [1,2,3,4,5,6];
        let taz = [7,8,9,10,11,12];
        let mut foo = Vec::new();
        foo.extend_from_slice(&bar[1..5]);
        foo.extend_from_slice(&taz[1..5]);
    }
    

    之后,可以使用 into_boxed_slice() 将其转换为固定长度的切片 . 或者,如果需要数组,可以使用我在another question中找到的函数:

    use std::convert::AsMut;
    
    fn clone_into_array<A, T>(slice: &[T]) -> A
        where A: Sized + Default + AsMut<[T]>,
              T: Clone
    {
        let mut a = Default::default();
        <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
        a
    }
    

    然后从之前转换 Vec 如下:

    let fixed: [i32; 8] = clone_into_array(&foo);
    

相关问题