题目概括:
背景是开发一个操纵机器人行动的脚本语言RoboScript, 实现以下语言标准
F
- Move forward by 1 step in the direction that it is currently pointing. Initially, the robot faces to the right.L
- Turn "left" (i.e. rotate 90 degrees anticlockwise)R
- Turn "right" (i.e. rotate 90 degrees clockwise)Fn
- Execute theF
commandn
times (NOTE:n
may be more than 1 digit long!)Ln
- ExecuteL
n timesRn
- ExecuteR
n times-
(SEQUENCE_OF_COMMANDS)n ... is equivalent to ...SEQUENCE_OF_COMMANDS...SEQUENCE_OF_COMMANDS (repeatedly executed "n" times)
-
p(n)<CODE_HERE>q WHERE:
p
is a "keyword" that declares the beginning of a pattern definition (much like thefunction
keyword in JavaScript or thedef
keyword in Python)(n)
is any non-negative integer (without the round brackets) which acts as a unique identifier for the pattern (much like a function/method name)<CODE_HERE>
is any valid RoboScript code (without the angled brackets)q
is a "keyword" that marks the end of a pattern definition (like theend
keyword in Ruby)
难点:
1、嵌套括号的处理,例如p312(F2LF2R)2qP312((F2R3)3)
2、题目中的要实现类似函数功能的`pattern`,这类似于函数,可在指令序列的任意地方声明和调用,极大增加了代码结构的复杂性
3、解析指令序列后,要求输出Robot的行动轨迹,需要一点技巧
4、OOP编程的抽象能力
思路:
1、解释:采集字符串的数据格式化为指令类,但把p指令单独看待,将p声明的pattern放入Map<Integer, Sentence> patternPool中,以便P指令调用
2、执行:定义Sentence类,储存语句类型(FLR,或者FLR组成的序列),执行次数n。定义execute方法调用n次相应的指令类的execute方法。
2、画图:定义Position类, 将Robot走过的position放入HashSet中,同时记录Robot走过的上下左右边界。有了走过的坐标和走过的区域边界就能模拟出Robot的活动路径了。
边界值用两层检查坐标有没有在HashSet内, 有则StringBuider append “*”,没有则append “空格”。
提交时发现的错误以及处理方式:
1、爆内存
傻傻的定义了巨无霸二维数组来模拟走过的路径,后改用HashSet只记录已走过的点坐标得到解决
1、StackOverflowError
好的家伙这12个样例没有过,点开错误一看,奥原来是StackOverflowError,痛骂样例嵌套了了几千层括号祸害我。
再仔细一看,奥原来是无限套娃调用要抛出异常 :)
故在P类中添加该checkAndPreventLoop方法检查是否有套娃现象。解决!
private static class P implements Instruction {
@Override
public void execute(int args) {
if (!patternPool.containsKey(args))
throw new RuntimeException("Unknown pattern: " + args);
checkAndPreventLoop(args, new HashSet<>());
patternPool.get(args).execute();
}
private void checkAndPreventLoop(int patternId, Set<Integer> visitedPatterns) {
if (visitedPatterns.contains(patternId)) {
throw new RuntimeException("Pattern recursion loop detected: " + patternId);
}
visitedPatterns.add(patternId);
Sentence pattern = patternPool.get(patternId);
for (Sentence s : pattern.nestedSentences) {
if (s.instructionType == InstructionType.P) {
checkAndPreventLoop(s.arg, new HashSet<>(visitedPatterns));
}
}
}
}
绿油油的很好!下一个!
最终代码:
import java.util.*;
public class RoboScript3 {
private static final Map<InstructionType, Instruction> instructionMap = new EnumMap<>(InstructionType.class);
private static final Map<Integer, Sentence> patternPool = new HashMap<>();
private static Direction movingDirection = Direction.E;
private static final Set<Position> visitedPositions = new HashSet<>();
private static final int[] position = {0, 0};
private static int minX = 0, minY = 0, maxX = 0, maxY = 0;
enum Direction {S, W, N, E};
enum InstructionType {F, L, R, P}
static {
instructionMap.put(InstructionType.F, new F());
instructionMap.put(InstructionType.L, new L());
instructionMap.put(InstructionType.R, new R());
instructionMap.put(InstructionType.P, new P());
}
public static String execute(String code) {
movingDirection = Direction.E;
visitedPositions.clear();
patternPool.clear();
position[0] = position[1] = 0;
minX = minY = 0;
maxX = maxY = 0;
visitedPositions.add(new Position(0, 0));
List<Sentence> program = compile(code);
for (Sentence s : program) {
s.execute();
}
return drawRoutine();
}
private static List<Sentence> compile(String code) {
List<Sentence> sentenceList = new ArrayList<>();
int i = 0;
while (i < code.length()) {
if (code.charAt(i) == '(') {
int j = i;
int bracketCounter = 0;
while (j < code.length()) {
if (code.charAt(j) == '(') {
bracketCounter++;
} else if (code.charAt(j) == ')') {
bracketCounter--;
if (bracketCounter == 0) {
break;
}
}
j++;
}
int k = j + 1;
while (k < code.length() && Character.isDigit(code.charAt(k))) {
k++;
}
int repeat = k > j + 1 ? Integer.parseInt(code.substring(j + 1, k)) : 1;
sentenceList.add(new Sentence(compile(code.substring(i + 1, j)), repeat));
i = k;
} else if (code.charAt(i) == 'p') { //pn<sentence>q
// Declare pattern
int j = i + 1; // skip 'p'
while (j < code.length() && Character.isDigit(code.charAt(j))) {
j++;
}
// if (j <= i + 1 || j + 1 >= code.length())
// throw new RuntimeException("Syntax Error: pn<sentence>q");
int patternId = Integer.parseInt(code.substring(i + 1, j));
// Find q position
int q = j;
while (q < code.length() && code.charAt(q) != 'q') {
q++;
}
if (patternPool.containsKey(patternId))
throw new RuntimeException("The pattern " + patternId + " has been defined before");
patternPool.put(patternId, new Sentence(compile(code.substring(j, q)), 1));
i = q + 1;
} else {
int j = i + 1;
while (j < code.length() && Character.isDigit(code.charAt(j))) {
j++;
}
int repeat = j > i + 1 ? Integer.parseInt(code.substring(i + 1, j)) : 1;
sentenceList.add(new Sentence(InstructionType.valueOf(String.valueOf(code.charAt(i))), repeat));
i = j;
}
}
return sentenceList;
}
private static String drawRoutine() {
StringBuilder builder = new StringBuilder((maxY - minY + 1) * (maxX - minX + 2));
for (int i = minX; i <= maxX; i++) {
for (int j = minY; j <= maxY; j++) {
if (visitedPositions.contains(new Position(i, j))) {
builder.append('*');
} else {
builder.append(' ');
}
}
builder.append("\r\n");
}
return builder.toString().replaceAll("\r\n$", "");
}
private static class Sentence {
private final InstructionType instructionType;
private final List<Sentence> nestedSentences;
private final int arg;
public Sentence(InstructionType instructionType, int arg) {
this.instructionType = instructionType;
this.nestedSentences = null;
this.arg = arg;
}
public Sentence(List<Sentence> nestedSentences, int arg) {
this.instructionType = null;
this.nestedSentences = nestedSentences;
this.arg = arg;
}
public void execute() {
if (instructionType != null) {
Instruction instruction = instructionMap.get(instructionType);
instruction.execute(arg);
} else {
for (int i = 0; i < arg; i++) {
for (Sentence sentence : nestedSentences) {
sentence.execute();
}
}
}
}
}
interface Instruction {
void execute(int args);
}
private static class F implements Instruction {
@Override
public void execute(int args) {
for (int i = 0; i < args; i++) {
switch (movingDirection) {
case E:
position[1]++;
break;
case N:
position[0]--;
break;
case S:
position[0]++;
break;
case W:
position[1]--;
}
visitedPositions.add(new Position(position[0], position[1]));
maxX = Math.max(maxX, position[0]);
minX = Math.min(minX, position[0]);
maxY = Math.max(maxY, position[1]);
minY = Math.min(minY, position[1]);
}
}
}
private static class R implements Instruction {
@Override
public void execute(int args) {
Direction[] directions = Direction.values();
int newIndex = (movingDirection.ordinal() + args) % 4;
movingDirection = directions[newIndex];
}
}
private static class L implements Instruction {
@Override
public void execute(int args) {
Direction[] directions = Direction.values();
int newIndex = Math.floorMod(movingDirection.ordinal() - args, 4);
movingDirection = directions[newIndex];
}
}
private static class P implements Instruction {
@Override
public void execute(int args) {
if (!patternPool.containsKey(args))
throw new RuntimeException("Unknown pattern: " + args);
checkAndPreventLoop(args, new HashSet<>());
patternPool.get(args).execute();
}
private void checkAndPreventLoop(int patternId, Set<Integer> visitedPatterns) {
if (visitedPatterns.contains(patternId)) {
throw new RuntimeException("Pattern recursion loop detected: " + patternId);
}
visitedPatterns.add(patternId);
Sentence pattern = patternPool.get(patternId);
for (Sentence s : pattern.nestedSentences) {
if (s.instructionType == InstructionType.P) {
checkAndPreventLoop(s.arg, new HashSet<>(visitedPatterns));
}
}
}
}
// private static class P implements Instruction {
// @Override
// public void execute(int args) {
// if (!patternPool.containsKey(args))
// throw new RuntimeException("Unknown pattern: " + args);
//
// patternPool.get(args).execute();
// }
// }
private static class Position {
private final int x;
private final int y;
public Position(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Position position = (Position) o;
return x == position.x && y == position.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
}