Vector 如何方便地将二维数组转换为二维向量?

Vector 如何方便地将二维数组转换为二维向量?,vector,rust,conways-game-of-life,Vector,Rust,Conways Game Of Life,我正在遵循这一原则,我希望能够在生命游戏中轻松地为宇宙添加一艘船(一个真正的形状) 作为第一步,我想将表示形状的0或1二维数组转换为表示宇宙中形状坐标的索引向量 我有一段工作代码,但我想让它更方便用户: const WIDTH: u32 = 64; const HEIGHT: u32 = 64; /// glider: [[0, 1, 0], [0, 0, 1], [1, 1, 1]] fn make_ship(shape: Vec<Vec<u32>>) -> V

我正在遵循这一原则,我希望能够在生命游戏中轻松地为宇宙添加一艘船(一个真正的形状)

作为第一步,我想将表示形状的
0
1
二维数组转换为表示宇宙中形状坐标的索引向量

我有一段工作代码,但我想让它更方便用户:

const WIDTH: u32 = 64;
const HEIGHT: u32 = 64;

/// glider: [[0, 1, 0], [0, 0, 1], [1, 1, 1]]
fn make_ship(shape: Vec<Vec<u32>>) -> Vec<u32> {
    let mut ship: Vec<u32> = Vec::new();

    for row_idx in 0..shape.len() {
        for col_idx in 0..shape[row_idx].len() {
            let cell = shape[row_idx][col_idx];
            if cell == 1 {
                ship.push(col_idx as u32 + row_idx as u32 * WIDTH);
            }
        }
    }

    ship
}

#[test]
fn glider() {
    let glider  = vec![vec![0, 1, 0], vec![0, 0, 1], vec![1, 1, 1]];
    println!("{:?}", make_ship(glider));
}

问题是:如何用简单的数组很好地表达一个形状,并具有函数
make_ship
获取任意大小的二维向量?

减少
向量的数量s可通过自定义:

在Rust的迭代器的帮助下,您的初始函数还可以使用一些改进:

fn make_ship(shape: Vec<Vec<u32>>) -> Vec<u32> {
    shape
        .iter()
        .enumerate()
        .flat_map(|(row, v)| {
            v.iter().enumerate().filter_map(move |(col, x)| {
                if *x == 1 {
                    Some(col as u32 + row as u32 * WIDTH)
                } else {
                    None
                }
            })
        })
        .collect()
}
fn make_ship<'a, T, U>(shape: &'a T) -> Vec<u32>
where
    &'a T: std::iter::IntoIterator<Item = U>,
    U: std::iter::IntoIterator<Item = &'a u32>,
{
    let mut ship: Vec<u32> = Vec::new();

    for (row_idx, row) in shape.into_iter().enumerate() {
        for (col_idx, &cell) in row.into_iter().enumerate() {
            if cell == 1 {
                ship.push(col_idx as u32 + row_idx as u32 * WIDTH);
            }
        }
    }

    ship
}
fn制造船舶(形状:Vec)->Vec{
形状
.国际热核实验堆(iter)
.enumerate()
.平面图(|(第五排)|{
v、 iter().enumerate().filter|u映射(move |(col,x)|{
如果*x==1{
部分(列为u32+行为u32*宽度)
}否则{
没有一个
}
})
})
.collect()
}
Vec
实际上不是二维向量,而是“向量向量”。这具有重要意义(假设外部向量解释为行,内部向量解释为列):

  • 行可以有不同的长度。这通常不是你想要的
  • 行是单个对象,可能分散在堆内存中
  • 为了访问一个元素,您必须遵循两种间接方式
  • 我将实现一个二维向量,而不是一个一维向量,其中包含关于其维度的附加信息。比如:

    struct Vec2D<T> {
        n_rows: usize,  // number of rows
        n_cols: usize,  // number of columns (redundant, since we know the length of data)
        data: Vec<T>,   // data stored in a contiguous 1D array
    }
    
    或者更方便地使用接受数组数组的函数或宏。(请参阅以获取灵感)

    要访问结构中的元素,必须使用一点数学知识将二维索引转换为一维索引:

    impl<T> Vec2D<T> {
        fn get(&self, row: usize, col: usize) -> &T {
             assert!(row < self.n_rows);
             assert!(col < self.n_cols);
             &self.data[row * self.n_cols + col]
        }
    }
    
    impl-Vec2D{
    fn get(&self,行:usize,列:usize)->&T{
    断言!(行

    虽然实现您自己的二维数组类型是一个有趣的练习,但为了高效使用,使用现有解决方案(如)可能更有效。

    另一个解决方案是使用
    AsRef
    透明地处理
    Vec
    [T]

    fn make_ship<T>(shape: &[T]) -> Vec<u32>
    where
        T: AsRef<[u32]>,
    {
        let mut ship: Vec<u32> = Vec::new();
    
        for row_idx in 0..shape.len() {
            let row = shape[row_idx].as_ref();
            for col_idx in 0..row.len() {
                let cell = row[col_idx];
                if cell == 1 {
                    ship.push(col_idx as u32 + row_idx as u32 * WIDTH);
                }
            }
        }
    
        ship
    }
    
    更好的解决方案是根本不关心切片/向量,而是使用迭代器:

    fn make_ship(shape: Vec<Vec<u32>>) -> Vec<u32> {
        shape
            .iter()
            .enumerate()
            .flat_map(|(row, v)| {
                v.iter().enumerate().filter_map(move |(col, x)| {
                    if *x == 1 {
                        Some(col as u32 + row as u32 * WIDTH)
                    } else {
                        None
                    }
                })
            })
            .collect()
    }
    
    fn make_ship<'a, T, U>(shape: &'a T) -> Vec<u32>
    where
        &'a T: std::iter::IntoIterator<Item = U>,
        U: std::iter::IntoIterator<Item = &'a u32>,
    {
        let mut ship: Vec<u32> = Vec::new();
    
        for (row_idx, row) in shape.into_iter().enumerate() {
            for (col_idx, &cell) in row.into_iter().enumerate() {
                if cell == 1 {
                    ship.push(col_idx as u32 + row_idx as u32 * WIDTH);
                }
            }
        }
    
        ship
    }
    
    fn制造/装运车辆
    哪里
    &'at:std::iter::IntoIterator,
    
    U:std::iter::IntoIterator这不是答案,但有一点:我会避免使用if,让循环也分配0。不必要的分支预测调用,以及一旦成为“rust”的一部分,就可能导致无效的矢量化。现代CPU真的很擅长移动内存,所以我希望它不会慢下来,至少。@ljedrz谢谢编辑,英语不是我的主要语言哈哈。@Tigran好吧,我只是对我应该放置活细胞的索引感兴趣,所以分配0对我来说只是一种噪音。“对于生产性使用,使用现有的解决方案(如ndarray板条箱)可能更有效。”并且有效地
    get()
    是一个成本很高的函数,ndarray将有很多更好的迭代方法,而不必总是进行乘法运算。@Stargateur是的,绝对是。但是,没有什么可以阻止我们创建自己的优化解决方案。问题是人们多么渴望重新发明轮子:)谢谢@kazemakase,我确实可以使用ndarray板条箱(不知道)但在我学习的过程中,我不需要额外的工具。我想你的宏正是我所需要的:语法糖。谢谢!我也会学习更多的宏。关于你答案的第二部分,你的建议在哪方面是一种改进?速度?内存使用率?可读性?@DjebbZ它更为惯用,并且不再需要边界检查(更好的性能)。
    let glider = vec![vec![0, 1, 0], vec![0, 0, 1], vec![1, 1, 1]];
    let glider = [[0, 1, 0], [0, 0, 1], [1, 1, 1]];
    let glider = [vec![0, 1, 0], vec![0, 0, 1], vec![1, 1, 1]];
    let glider = vec![[0, 1, 0], [0, 0, 1], [1, 1, 1]];
    
    fn make_ship<'a, T, U>(shape: &'a T) -> Vec<u32>
    where
        &'a T: std::iter::IntoIterator<Item = U>,
        U: std::iter::IntoIterator<Item = &'a u32>,
    {
        let mut ship: Vec<u32> = Vec::new();
    
        for (row_idx, row) in shape.into_iter().enumerate() {
            for (col_idx, &cell) in row.into_iter().enumerate() {
                if cell == 1 {
                    ship.push(col_idx as u32 + row_idx as u32 * WIDTH);
                }
            }
        }
    
        ship
    }