首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >预测火星机器人的位置

预测火星机器人的位置
EN

Code Review用户
提问于 2019-03-03 21:38:41
回答 2查看 1.6K关注 0票数 6

描述

一个机器人降落在火星上,恰好是一个笛卡尔网格;假设我们把这些指令交给机器人,比如LFFFRFFFRRFFF,其中"L“是”向左转90度“,"R”是“向右转90度”,"F“是”前进一个空格“,请为机器人编写控制代码,以便它最终到达正确的目的地,并包括单元测试。

下面是命令“FF”的示例输出:

0,2

代码语言:javascript
复制
class Robot {
  private int x;
  private int y;
  private int currentDirection;

  Robot() {
    this(0, 0);
  }

  Robot(int x, int y) {
    this.x = x;
    this.y = y;
    currentDirection = 0;
  }

  public void move(String moves) {
    for (char ch : moves.toCharArray()) {
      if (ch == 'R') currentDirection += 1;
      if (ch == 'L') currentDirection -= 1;

      currentDirection = currentDirection % 4;

      if (ch != 'F') continue;

      System.out.println(currentDirection);
      if (currentDirection == 0) {
        y += 1;
      } if (currentDirection == 1) {
        x += 1;
      } if (currentDirection == 2) {
        y -= 1;
      } if (currentDirection == 3) {
        x -= 1;
      }
    }
  }

  public void reset() {
    x = 0;
    y = 0;
    currentDirection = 0;
  }

  public String position() {
    return x + ":" + y;
  }
}

class Main {
  public static void main(String[] args) {
    Robot robot = new Robot();
    robot.move("FF");
    System.out.println(robot.position()); // 0,2
    robot.reset();
    System.out.println(robot.position()); // 0,0
    robot.move("FFRF");
    System.out.println(robot.position()); // 1,2
    robot.reset();
    robot.move("FFRRRFF");
    System.out.println(robot.position()); // -2,2
  }
}

如何使这段代码更面向对象?

EN

回答 2

Code Review用户

发布于 2019-03-03 22:59:12

我会考虑将方向重构成一个枚举,而不是一个神奇的数字。这将允许在这种类型中封装相关的逻辑。

看起来可能是这样的:

代码语言:javascript
复制
    enum Direction {
        UP(0, 1),
        RIGHT(1, 0),
        DOWN(0, -1),
        LEFT(-1, 0);

        final int xVector;
        final int yVector;

        private final Direction FIRST = Direction.values()[0];
        private final Direction LAST = Direction.values()[Direction.values().length];

        Direction(int xVector, int yVector) {
            this.xVector = xVector;
            this.yVector = yVector;
        }

        Direction rotatedLeft() {
            return this == FIRST
                    ? LAST // cycle complete
                    : Direction.values()[ordinal() - 1]; // previous value
        }

        Direction rotatedRight() {
            return this == LAST
                    ? FIRST // cycle complete
                    : Direction.values()[ordinal() + 1]; // next value
        }
    }

然后Robot代码变得(包括一些与面向对象方面本身无关的主观清理):

代码语言:javascript
复制
class Robot {
    private int x;
    private int y;
    private Direction currentDirection;

    Robot() {
        this(0, 0);
    }

    Robot(int x, int y) {
        this.x = x;
        this.y = y;
        currentDirection = Direction.UP;
    }

    public void move(String moves) {
        for (char code : moves.toCharArray()) {
            // a matter of taste, I like to extract logic 
            // out of looping constructs for clarity
            move(code);
        }
    }

    private void move(char code) {
        switch (code) {
            case 'R': {
                currentDirection = currentDirection.rotatedRight();
                break;
            }
            case 'L': {
                currentDirection = currentDirection.rotatedLeft();
                break;
            }
            case 'F': {
                // you'd call the println thing here.
                // as it's not part of the requirements I assumed it to be a debugging artifact
                this.x += currentDirection.xVector;
                this.y += currentDirection.yVector;
                break;
            }
        }
    }

    public void reset() {
        x = 0;
        y = 0;
        currentDirection = Direction.UP;
    }

    public String position() {
        return x + ":" + y;
    }
}

我认为这更多的是面向对象的,也是一个改进。

您可以更进一步,将xy坐标封装在一个小的独立类(Position)中。就像这样:

代码语言:javascript
复制
    class Position {
        static final Position DEFAULT = new Position(0, 0);

        final int x;
        final int y;

        public Position(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public Position movedInto(Direction direction) {
            return new Position(x + direction.xVector, y + direction.yVector);
        }

        @Override
        public String toString() {
            return x + ":" + y;
        }
    }

然后,在Robot类中,用Position position替换xy字段,不再重新计算Robot类中的位置,因为您可以简单地这样做:

代码语言:javascript
复制
case 'F': {
    position = position.movedInto(currentDirection);
    break;
}

move方法中。

加:

代码语言:javascript
复制
    public void reset() {
        position = Position.DEFAULT;
        currentDirection = Direction.UP;
    }

    public String position() {
        return position.toString();
    }

另一个想法是将移动代码封装到一个或多个类中。有时很难说出在哪里划分界限,因为代码越来越多的OO是以更多的样板为代价的,并且可能是一种过度工程的形式。

我承认我还没有测试我的重构版本,我只是从代码设计的角度来探讨这个问题。

票数 8
EN

Code Review用户

发布于 2019-03-03 23:06:52

Initialization

正如所写的,你的机器人着陆器可以降落在任何X,Y坐标在你的网格,但将永远面对正的Y轴方向。这似乎不合理。如果风、湍流会导致位置不确定,这需要在一个特定的X,Y坐标上初始化机器人,那么假设它也可能降落在任何一个平面上似乎是合理的。

代码语言:javascript
复制
Robot() {
   this(0, 0, 0);
}

Robot(int x, int y) {
    this(x, y, 0);
}

Robot(int x, int y, int initial_facing) {
    // ...
}

命令和指令

您的指令是一系列单字母命令,但实际上不能向机器人发送一个命令。您应该将单独的命令与一系列的说明分开。类似于:

代码语言:javascript
复制
void turn_left() { ... }
void turn_right() { ... }
void move_forward() { ... }

public void command(char command_letter) {
    switch (command_letter) {
        case 'L': turn_left(); break;
        case 'R': turn_right(); break;
        case 'F': move_forward(); break;
        default: throw new IllegalArgumentException("Invalid command: "+command_letter);
    }
}

public void instructions(String moves) {
    for (char command_letter : moves.toCharArray()) {
        command(command_letter);
    }
}

,我在哪里?

position()返回一个包含机器人坐标的String。如果控制程序想要查询机器人的位置,为了确定应该发送哪些命令将其发送到所需的位置,则需要将该字符串解析为整数值。

考虑返回实际整数位置,可能如下:

代码语言:javascript
复制
public int[] position() {
    return new int[]{ x, y };
}

或者,您可能希望创建一个class Position,它可以存储机器人的x,y位置。或者你可以使用java.awt.Point

代码语言:javascript
复制
public Point position() {
    return new Point(x, y);
}

也许可以重写toString()方法来返回对机器人的友好描述,包括它的位置。或者是一种position_as_string()方法。

机器人面向哪边?不能直接说出来!目前您必须查询position(),然后是move("F"),然后是position(),然后比较位置以确定机器人面临的方向!添加一个facing()方法怎么样?

学习如何编写适当的单元测试。例如,使用JUnit5,您可以编写:

代码语言:javascript
复制
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class RobotTests {

    private final Robot robot = new Robot();

    @Test
    void starts_at_origin() {
        assertEquals("0:0", robot.position());
    }

    @Test
    void move_forward_twice() {
        robot.move("FF");
        assertEquals("0:2", robot.position());
    }

    @Test
    void move_and_turn_right() {
        robot.move("FFRF");
        assertEquals("1:2", robot.position());
    }

    @Test
    void three_rights_make_a_left() {
        robot.move("FFRRRFF");
        assertEquals("-2:2", robot.position());
    }

    @Test
    void but_one_left_does_not() {
        robot.move("FFLFF");
        assertEquals("-2:2", robot.position());
    }
}

注意,每个测试都在一个全新的RobotTests实例中运行,因此不需要在每个实例之间调用robot.reset()

如果你运行这个单元测试,你会发现5个测试中有4个通过了,一个测试失败了。我让你自己找出原因。

附加关注

currentDirection采用0123值来表示四个基本方向是有限的。如果以后要添加对角线移动(NWSWSENE),您会使用值4及以上来表示它们吗?还是将原来的4个方向重新编号为0246,并使用1357作为对角线方向?

您可能会倾向于使用enum作为方向值,但我认为这不是一个好主意。我将使用090180270作为方向值。这些都有物理意义。如果以后你的机器人被允许使用一个更真实的double x, y;坐标系,你也可以改变为使用double currentDirection;,并允许一个零度的旋转。使用enum,您将失去这种未来可能的灵活性。

作为另一种选择,您还可以考虑使用定向向量:

代码语言:javascript
复制
int dx=0, dy=1;   // currentDirection = 0°

move_forward简单地变成:

代码语言:javascript
复制
x += dx;
y += dy;

然后右转就会变成:

代码语言:javascript
复制
int old_dx = dx;

dx = dy;
dy = -old_dx;
票数 8
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/214669

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档