[炼手Rust]机器人模拟器

106 阅读3分钟

Hi,大家好,这里是炼手Rust专栏,我是xiaoK,今天来看个rust语言编程挑战:机器人模拟器。

ksr18-2.jpg

提问

阿峰需要编写一个机器人模拟器。

机器人工厂的测试设施需要一个程序来验证机器人的运动。机器人有三种可能的运动:

右转
左转
前进

机器人被放置在一个假设的无限网格上,在一组 {x,y} 坐标处面向特定方向(北、东、南或西),例如坐标 {3,8},当面向北和东时,前进时坐标递增(可以想象一个平面坐标系,X 轴和 Y 轴的方向分别指向东和北)。

然后,机器人会收到许多指令,此时测试设备会验证机器人的新位置及其指向的方向。

例:
字母串“RAALAL”的含义是:

右转
提前两次
左转
前进一次
再次左转  

假设机器人从 {7, 3} 开始,面向北。然后运行该指令流应使其位于面向西的 {9, 4}。

模板:

#[derive(PartialEq, Eq, Debug)]
pub enum Direction {
    North,
    East,
    South,
    West,
}

pub struct Robot;

impl Robot {
    pub fn new(x: i32, y: i32, d: Direction) -> Self {
        todo!("Create a robot at (x, y) ({x}, {y}) facing {d:?}")
    }

    #[must_use]
    pub fn turn_right(self) -> Self {
        todo!()
    }

    #[must_use]
    pub fn turn_left(self) -> Self {
        todo!()
    }

    #[must_use]
    pub fn advance(self) -> Self {
        todo!()
    }

    #[must_use]
    pub fn instructions(self, instructions: &str) -> Self {
        todo!("Follow the given sequence of instructions: {instructions}")
    }

    pub fn position(&self) -> (i32, i32) {
        todo!()
    }

    pub fn direction(&self) -> &Direction {
        todo!()
    }
}

分析

观察模板,我们需要实现 7 个方法:

  • new方法需要创建一个Robot对象。
  • turn_right变更方向即可,并且获得原来对象的所有权,返回一个新的对象。
  • turn_leftturn_right类似。
  • advance针对当前的方向,在x或者y上,加 1 或者减 1 。
  • instructions方法,遍历当前指令的字符,针对每个字符,调用上述方法即可,很简单。
  • position方法返回当前对象的坐标。
  • direction方法返回当前对象的方向引用。

在简单的分析完成之后,我们就可以给出解决方案了。

解决方案

#[derive(PartialEq, Eq, Debug)]
pub enum Direction {
    North,
    East,
    South,
    West,
}

pub struct Robot {
    x: i32,
    y: i32,
    d: Direction,
}

impl Robot {
    pub fn new(x: i32, y: i32, d: Direction) -> Self {
        Self { x, y, d }
    }


    pub fn turn_right(self) -> Self {
        match self.d {
            Direction::North => Self { d: Direction::East, ..self },
            Direction::East => { Self { d: Direction::South, ..self } }
            Direction::South => { Self { d: Direction::West, ..self } }
            Direction::West => { Self { d: Direction::North, ..self } }
        }
    }


    pub fn turn_left(self) -> Self {
        match self.d {
            Direction::North => Self { d: Direction::West, ..self },
            Direction::East => { Self { d: Direction::North, ..self } }
            Direction::South => { Self { d: Direction::East, ..self } }
            Direction::West => { Self { d: Direction::South, ..self } }
        }
    }


    pub fn advance(self) -> Self {
        match self.d {
            Direction::North => Self { y: self.y + 1, ..self },
            Direction::East => { Self { x: self.x + 1, ..self } }
            Direction::South => { Self { y: self.y - 1, ..self } }
            Direction::West => { Self { x: self.x - 1, ..self } }
        }
    }


    pub fn instructions(self, instructions: &str) -> Self {
        let mut robot = self;
        for x in instructions.chars() {
            match x {
                'L' => { robot = robot.turn_left() }
                'R' => { robot = robot.turn_right() }
                'A' => { robot = robot.advance() }
                _ => {}
            }
        }
        robot
    }

    pub fn position(&self) -> (i32, i32) {
        (self.x, self.y)
    }

    pub fn direction(&self) -> &Direction {
        &self.d
    }
}

Trick

  1. 使用match表达式,判断枚举分支。
  2. 结构体有个非常好用的复制方式,叫结构体更新语法。例如Self { x: self.x + 1, ..self }。此处只更新了x的值,其他值与原来保持一致。
  3. 注意区分借用和移动语法,此模板的方法中:newturn_rightturn_leftadvanceinstructions使用了移动的语法,调用这些语法,会消耗掉原来的对象,原来的对象将不再可用。