实现用于访问1D向量的2D向量语法?

时间:2017-10-03 13:53:43

标签: vector syntax rust operator-overloading

我正在制作一个玩具roguelike,并且有一个Level结构用于存储游戏地图,其中最天真的实现是2D矢量。

我跟随使用Vector Vector的{​​{3}},但声明为了提高性能,我们也可以使用Vector MAP_HEIGHT * MAP_WIDTH大小为(x, y),并且要访问map[y * MAP_WIDTH + x]的图块,只需访问Index即可。

我试图实现这种更快的方法,但使用getter和setter是笨重的,公共字段也不是很好。我更喜欢它感觉像2D矢量。

为了做到这一点,我需要为我的课程实现impl特质,但我不确定如何获得我想要的结果。也许通过嵌套Index s?我真的不知道。

这是我的代码尝试为我的结构实施const MAP_WIDTH: i32 = 80; const MAP_HEIGHT: i32 = 45; pub struct Level { map: Vec<Tile>, } impl Level { pub fn new() -> Self { Level { map: vec![Tile::empty(); (MAP_HEIGHT * MAP_WIDTH) as usize] } } } impl std::ops::Index<i32> for Level { type Output = Tile; fn index(&self, x: i32) -> &Self::Output { self[MAP_WIDTH + x]; // We have x and y values; how do we make this work? } } ,这显然不符合我的目的,因为它是一维的:

{{1}}

3 个答案:

答案 0 :(得分:5)

使您的结构可以索引(i32, i32)类型的对象。

type Pos = (i32, i32);

impl std::ops::Index<Pos> for Level {
    type Output = Tile;
    fn index(&self, (x, y): Pos) -> &Self::Output {
        &self.map[(y * MAP_WIDTH + x) as usize]
    }
}

然后您可以访问,例如:

let tile = level[(3, 4)];

由于您使用的是i32,因此您需要确保这些值在范围内,并且可以强制转换为usize,这是Vec被索引的内容。可能你应该从一开始就坚持使用u32usize值。否则,您需要跟踪最小xy值,并减去它们,以保持位置在范围内。处理正坐标并假设地图的角是(0, 0)这个假设非常简单。

答案 1 :(得分:5)

虽然不是很明显,但这是可能的。

首先,我建议在MAP_WIDTH中使用MAP_HEIGHTusize,因为它们是正整数:

const MAP_WIDTH: usize = 80;
const MAP_HEIGHT: usize = 45;

然后你需要实现Index(可能还有IndexMut)来返回一个切片;在这种情况下,我假设您希望第一个坐标为行:

impl std::ops::Index<usize> for Level {
    type Output = [Tile];

    fn index(&self, row: usize) -> &[Tile] {
        let start = MAP_WIDTH * row;
        &self.map[start .. start + MAP_WIDTH]
    }
}

impl std::ops::IndexMut<usize> for Level {
    fn index_mut(&mut self, row: usize) -> &mut [Tile] {
        let start = MAP_WIDTH * row;
        &mut self.map[start .. start + MAP_WIDTH]
    }
}

然后,当您索引Level时,它首先返回具有适用行的切片;然后你可以用列号索引那个切片。

以下是替代Tile的示例实现:

const MAP_WIDTH: usize = 80;
const MAP_HEIGHT: usize = 45;

#[derive(Clone, Debug)]
pub struct Tile {
    x: u32,
    y: u32
}

pub struct Level {
    map: Vec<Tile>,
}

impl Level {
    pub fn new() -> Self {
        Level { map: vec![Tile { x: 0, y: 0 }; (MAP_HEIGHT * MAP_WIDTH) as usize] }
    }
}

impl std::ops::Index<usize> for Level {
    type Output = [Tile];

    fn index(&self, row: usize) -> &[Tile] {
        let start = MAP_WIDTH * row;
        &self.map[start .. start + MAP_WIDTH]
    }
}

impl std::ops::IndexMut<usize> for Level {
    fn index_mut(&mut self, row: usize) -> &mut [Tile] {
        let start = MAP_WIDTH * row;
        &mut self.map[start .. start + MAP_WIDTH]
    }
}

fn main() {
    let mut lvl = Level::new(); 

    lvl[5][2] = Tile { x: 5, y: 2 };

    println!("{:?}", lvl[5][2]); // Tile { x: 5, y: 2 }
}

答案 2 :(得分:1)

如果不公开有关您的实施的内部细节,则无法执行此操作。 Index定义为:

pub trait Index<Idx> 
where
    Idx: ?Sized, 
{
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

为了支持game[x][y]game[x]的返回值需要:

  1. 是对某事的参考。 (&Self::Output
  2. 实施Index本身。
  3. 没有值可以返回self以外的引用,而self已经为Index实施了usize,因此您可以“{1}}重用它。

    相反,您可以为元组实现索引:

    impl std::ops::Index<(usize, usize)> for Level {
        type Output = Tile;
        fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
            &self.map[MAP_WIDTH as usize * y + x]
        }
    } 
    

    这可以用作level[(43, 12)]

    如果您implement Index to return a slice,您应该知道永远要求您的内部数据结构基于切片。例如,你不能使用&#34;稀疏&#34;结构类似于HashMap,因为它无法返回&[Tile]。返回&[Tile]的功能现在是结构的公共API 的一部分。这种表现形式肯定会发生变化,特别是因为它已经改变了一次。