【问题标题】:Implementing 2D vector syntax for accessing a 1D vector?实现 2D 矢量语法以访问 1D 矢量?
【发布时间】:2018-03-14 18:04:05
【问题描述】:

我正在制作一个玩具 roguelike,并有一个 Level 结构来存储游戏地图,其中最简单的实现是 2D 向量。

我正在关注this tutorial,它使用Vectors 的Vector,但指出为了提高性能,也可以使用大小为MAP_HEIGHT * MAP_WIDTH 的单个Vector,并在以下位置访问磁贴(x, y) 可以直接访问map[y * MAP_WIDTH + x]

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

为此,我需要为我的班级实现 Index 特征,但我不确定如何获得我想要的结果。也许通过嵌套impls?我真的不知道。

这是我的代码,尝试为我的结构实现Index,这显然不适合我的目的,因为它是一维的:

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?
    }
}

【问题讨论】:

    标签: vector syntax rust operator-overloading


    【解决方案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. 是对某事的引用。 (&amp;Self::Output)
    2. 自行实现Index

    除了self 之外,没有任何值可以返回引用,并且self 已经为usize 实现了Index,因此您不能重复使用它。

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

    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,您应该知道您永远要求您的内部数据结构是基于切片的。例如,您不能使用像HashMap 这样的“稀疏”结构,因为它不能返回&amp;[Tile]。返回&amp;[Tile] 的能力现在是结构的public API 的一部分。表示当然有可能会改变,尤其是因为它已经改变过一次。

    【讨论】:

      【解决方案2】:

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

      首先,我建议将MAP_WIDTHMAP_HEIGHT 放在usize 中,因为它们是正整数:

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

      【讨论】:

      • 在我的游戏Tiles 中存储两个bools、blockedblock_sight。有关他们职位的信息在Vector 本身。但感谢您的帮助。我想我现在只使用一个元组,因为它更容易理解,但我肯定会保存这个 sn-p 供以后使用。
      • @Boiethios 你说得对,这实际上解决了我问的问题,所以即使我将使用元组,我也会接受这个答案。
      • 您应该知道,通过这样做,您永远要求您的内部数据结构是基于切片的。例如,您不能使用像HashMap 这样的“稀疏”结构,因为它不能返回&amp;[Tile]。返回&amp;[Tile] 的能力现在是结构的public API 的一部分。表示当然有可能会改变,特别是因为 OP 已经改变了一次表示。
      • @Shepmaster 如果这是一个问题,OP 只需要创建自己的结构Row 来实现IndexLevel --index--> Row --index- -> Tile.
      【解决方案3】:

      使您的结构可索引(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,这是Vecs 的索引。也许你应该从一开始就坚持使用u32usize 值。否则,您需要跟踪最小 xy 值,然后减去它们,以保持位置在范围内。处理正坐标并假设地图的角为(0, 0) 肯定更简单。

      【讨论】:

      • 非常感谢!你是对的,因为libtcod0,0 映射到屏幕的左上角(这意味着你不能,或者不想低于0,0)我应该使用u32 而不是@ 987654336@.
      猜你喜欢
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 2021-02-14
      • 1970-01-01
      • 2016-04-25
      • 1970-01-01
      • 1970-01-01
      • 2018-06-11
      相关资源
      最近更新 更多