高效的方法来判断一个坐标是否为顶点/边界坐标?

3
想象一个笛卡尔平面,每个Cell对象代表平面中的一个点(该平面将成为迷宫)。在构建迷宫时,我想知道Cell对象是顶点(四个角点)还是边界点(迷宫边缘上的任何单元格,顶点也是边界点)。
我需要知道这一点,以便将相邻单元格添加为特定单元格的邻居(我正在创建具有节点的图结构)。不同的边界对于哪些单元格是邻居有不同的要求(例如右上顶点不能有y + 1或x + 1作为邻居,因为它在迷宫外,而左下顶点不能有y - 1或x - 1)。
我尝试通过一堆if语句来实现这一点,但我觉得这不是一种很好的实践。所以我想问是否有更好的方法来确定点的坐标类型?
以下是我的实现方式:
private String typeOfBorderCell(Cell cell){
    if (!isBorderCell(cell)){
        throw new IllegalArgumentException("cell is not a border cell");
    }
    double x = cell.getCoordinate().getX();
    double y = cell.getCoordinate().getY();

    // Vertices
    if (x == 0 && y == 0){
        return "bottom-left";
    }

    else if (x == 0 && y == height - 1){
        return "top-left";
    }

    else if (x == width - 1 && y == 0){
        return "bottom-right";
    }

    else if (x == width - 1 && y == height - 1){
        return "top-right";
    }

    // Non-Vertices
    else if (x == 0 && (y > 0 && y < height - 1)){
        return "left";
    }

    // and so on for the other three non-vertex borders
}

高度/宽度是迷宫的大小,但由于迷宫坐标从原点(0,0)开始,因此我不得不减去1,因此一个5x5的迷宫其y的最大值为4,x的最大值也为4。

这样做,我将得到总共8个条件语句(使用此方法的方法还需要一个带有8个case的switch语句)。是否有更���效的方法在没有大量条件语句的情况下完成此操作?


如果你需要8种不同的返回类型,那么你就需要8个不同的条件语句。 - Matt Way
1个回答

0

我发现枚举是一个相当优雅的替代长长的一系列if语句的选择。以下是一个例子(使用Java 8):

enum CellType {

    OTHER(1, (x, y) -> true),
    TOP(2, (x, y) -> y == HEIGHT - 1),
    BOTTOM(2, (x, y) -> y == 0),
    LEFT(2, (x, y) -> x == 0),
    RIGHT(2, (x, y) -> x == WIDTH - 1),
    TOP_LEFT(3, TOP, LEFT),
    BOTTOM_RIGHT(3, BOTTOM, RIGHT),
    TOP_RIGHT(3, TOP, RIGHT),
    BOTTOM_LEFT(3, BOTTOM, LEFT);

    private static final int HEIGHT = 5;
    private static final int WIDTH = 5;

    private final int precedence;
    private final BiPredicate<Integer, Integer> test;

    private CellType(int precedence, BiPredicate<Integer, Integer> test) {
        this.precedence = precedence;
        this.test = test;
    }

    private CellType(int precedence, CellType type1, CellType type2) {
        this(precedence, type1.test.and(type2.test));
    }

    public static CellType valueOf(int x, int y) {
        assert x >= 0 && x < WIDTH && y >= 0 && y < WIDTH;
        return Arrays.stream(values())
            .filter(ct -> ct.test.test(x, y))
            .max(Comparator.comparingInt(ct -> ct.precedence))
            .orElse(OTHER);
    }
}

您可以在代码中使用类似 CellType.valueOf(0, 4) 的语句,它将返回 CellType.TOP_LEFT

我更喜欢这种习惯用法而不是一堆 if 语句,因为它将谓词放在一个地方,并使它们易于识别和更改。

这也将导致您的“单元格类型”不是字符串,如果您以后想要添加逻辑,则这是一个好主意。例如,您可以通过将处理单元格类型的逻辑添加到枚举本身中来避免您在问题中提到的 switch 语句。与字符串进行比较也很容易出错。您可能会更改一个字符串并最终出现难以检测的错误。如果您更改枚举,则会立即出现语法错误。

这是它的简要解释。一个 BiPredicate 是一个函数接口,它接受两个整数(x 和 y)并返回一个布尔值。每个 CellType 成员都有一个谓词,用于测试给定的 x 和 y 是否表示该类型的单元格。对于边缘单元格类型,使用 lambda 表达式提供条件。对于顶点,构造函数接受两个边缘单元格类型,并通过测试单元格是否满足两个边缘条件来构造一个新的谓词。例如,TOP_LEFT 测试单元格是否同时位于顶部和左侧边缘。 valueOf 方法查找所有满足给定单元格的单元格类型,然后返回具有最高优先级的单元格类型。优先级确保返回顶点而不是边缘。如果没有匹配的单元格类型,则返回 OTHER(非边缘非顶点)。

我期望CellType.valueOf(0, 4)返回CellType.TOP_LEFT,因为在坐标系中(对于一个5x5的迷宫), (0, 4) 实际上代表的是左侧而不是右侧,这应该对应着x值为4而不是0。 - user2580104
@Naz 你说得对 - 在谓词中我混淆了我的x和y值。希望你能理解我的意思。 - sprinter
我真的很喜欢这个设计,但我仍然不确定谓词的真正含义。我知道谓词是返回布尔值的函数,但是,我不确定 TOP((x, y) -> x <= 0 实际上在做什么。我无法理解它(或其他谓词)以及x小于或等于如何使其成为顶部。你能详细说明如何修复谓词以获得更准确的结果吗?我试着去琢磨它,但我想我还是没太理解。 - user2580104
1
我会调整代码,使其正确,并添加一些可能有帮助的注释。 - sprinter
谢谢!现在谓词更有意义了——这是一个优雅的解决方案,可以替代一堆难看的if语句。从这个解决方案中学到了很多东西。 - user2580104
@Naz 不用谢。还有一件事要记住:使用双精度浮点数进行比较容易出现错误。你应该将其转换为使用整数,或考虑将谓词改为不等式。 - sprinter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接