如何方便地将二维数组转换为二维向量?
How can I conveniently convert a 2-dimensional array into a 2-dimensional vector?
我正在关注 Rust-wasm tutorial,我希望能够在生活游戏中轻松地向宇宙中添加一艘船(实际上是一种形状)。
作为第一步,我想将表示形状的 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));
}
test
显示了我的问题:vec!
的冗长。理想情况下,我希望能够在没有所有 vec!
的情况下编写它。 make_ship
的代码不应该关心形状数组的大小。理想示例:
let glider = [[0, 1, 0],
[0, 0, 1],
[1, 1, 1],];
问题是:如何用简单的数组很好地表达一个形状,并让函数make_ship
接受任意大小的二维向量?
通过自定义 macro 可以减少 vec!
的数量:
#[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]]
}
您的初始函数也可以在 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()
}
Vec<Vec<_>>
其实不是二维向量而是"vector of vectors"。这具有重大意义(假设外部向量被解释为行,内部向量被解释为列):
- 行可以有不同的长度。这通常不是您想要的。
- 行是单独的对象,可能分散在整个堆内存中。
- 为了访问一个元素,您必须遵循两个间接寻址。
我会实现一个二维向量,而不是一个带有关于其维度的附加信息的一维向量。类似于:
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
}
这个结构可以用
初始化
let glider = Vec2D {
n_rows: 3,
n_cols: 3,
data: vec![0, 1, 0,
0, 0, 1,
1, 1, 1],
};
或者更方便地使用采用数组数组的函数或宏。 (请参阅 获取灵感)。
要访问结构中的元素,您必须使用一些数学知识将 2D 索引转换为 1D 索引:
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]
}
}
虽然实现您自己的二维数组类型是一项有趣的练习,但为了高效使用,使用现有解决方案(例如 ndarray crate.
可能更有效
另一种解决方案是使用 AsRef
:
透明地处理 Vec<T>
和 [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
}
这处理以下内容:
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]];
一个更好的解决方案是根本不关心 slices/vectors,而是使用迭代器:
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
}
它也可以处理上述情况,但如果它提供了这样的迭代器,也可以处理诸如@kazemakase 的 Vec2D
类型。
我正在关注 Rust-wasm tutorial,我希望能够在生活游戏中轻松地向宇宙中添加一艘船(实际上是一种形状)。
作为第一步,我想将表示形状的 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));
}
test
显示了我的问题:vec!
的冗长。理想情况下,我希望能够在没有所有 vec!
的情况下编写它。 make_ship
的代码不应该关心形状数组的大小。理想示例:
let glider = [[0, 1, 0],
[0, 0, 1],
[1, 1, 1],];
问题是:如何用简单的数组很好地表达一个形状,并让函数make_ship
接受任意大小的二维向量?
通过自定义 macro 可以减少 vec!
的数量:
#[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]]
}
您的初始函数也可以在 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()
}
Vec<Vec<_>>
其实不是二维向量而是"vector of vectors"。这具有重大意义(假设外部向量被解释为行,内部向量被解释为列):
- 行可以有不同的长度。这通常不是您想要的。
- 行是单独的对象,可能分散在整个堆内存中。
- 为了访问一个元素,您必须遵循两个间接寻址。
我会实现一个二维向量,而不是一个带有关于其维度的附加信息的一维向量。类似于:
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
}
这个结构可以用
初始化let glider = Vec2D {
n_rows: 3,
n_cols: 3,
data: vec![0, 1, 0,
0, 0, 1,
1, 1, 1],
};
或者更方便地使用采用数组数组的函数或宏。 (请参阅
要访问结构中的元素,您必须使用一些数学知识将 2D 索引转换为 1D 索引:
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]
}
}
虽然实现您自己的二维数组类型是一项有趣的练习,但为了高效使用,使用现有解决方案(例如 ndarray crate.
可能更有效另一种解决方案是使用 AsRef
:
Vec<T>
和 [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
}
这处理以下内容:
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]];
一个更好的解决方案是根本不关心 slices/vectors,而是使用迭代器:
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
}
它也可以处理上述情况,但如果它提供了这样的迭代器,也可以处理诸如@kazemakase 的 Vec2D
类型。