Search code examples
vectorrustconways-game-of-life

How can I conveniently convert a 2-dimensional array into a 2-dimensional vector?


I'm following the Rust-wasm tutorial and I want to be able to easily add a ship (a shape really) to the Universe in the game of life.

As a first step, I'd like to convert a 2-dimensional array of 0 or 1 representing a shape into a vector of indices representing the coordinates of the shape in the Universe.

I have a working piece of code but I'd like to make it a bit more user-friendly:

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));
}

The test shows my problem: the verbosity of vec!s. Ideally I'd like to be able to write it without all the vec!. The code of make_ship shouldn't care about the size of the shape arrays. Ideal example:

let glider = [[0, 1, 0],
              [0, 0, 1],
              [1, 1, 1],];

The question is: how to express a shape nicely with simple arrays and have the function make_ship take 2-dimensional vectors of arbitrary size?


Solution

  • Reducing the number of vec!s is possible with a custom macro:

    #[macro_export]
    macro_rules! vec2d {
        ($($i:expr),+) => { // handle numbers
            {
                let mut ret = Vec::new();
                $(ret.push($i);)*
                ret
            }
        };
    
        ([$($arr:tt),+]) => { // handle sets
            {
                let mut ret = Vec::new();
                $(ret.push(vec!($arr));)*
                ret
            }
        };
    }
    
    fn main() {
        let glider = vec2d![[0, 1, 0],
                            [0, 0, 1],
                            [1, 1, 1]];
    
        let glider2 = vec2d![[0, 1, 0, 1],
                             [0, 0, 1, 0],
                             [1, 1, 1, 0],
                             [0, 1, 1, 0]];
    
    
        println!("{:?}", glider);  // [[0, 1, 0], [0, 0, 1], [1, 1, 1]]
        println!("{:?}", glider2); // [[0, 1, 0, 1], [0, 0, 1, 0], [1, 1, 1, 0], [0, 1, 1, 0]]
    }
    

    Your initial function could also use a bit of an improvement with the help of Rust's iterators:

    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()
    }