Hashing是一个合适的解决方案吗?我过度复杂了吗?

时间:2013-10-08 09:17:23

标签: java arrays hash concept

我写了一个2D平台游戏,我需要有(最多4个)门的房间。我用Java编写它,但语言无关紧要。

每个房间可以有4个门,在顶部,底部和侧面。我称他们为NORTHSOUTHEASTWEST。当我正在建造一个房间时,我只给它一个整数,整数中的每个位代表一个门。

例如,如果我想要一个有3扇门的房间 (一个在北方,一个在东方,在西方) 我给房间的号码是:11(二进制1011)。

出于这个原因,每扇门都有一个整数,标识它。

NORTH = 8;//1000
SOUTH = 4;//0100
EAST =  2;//0010
WEST =  1;//0001

如果我生成一个房间,我会给它们这些标识符的组合。

例如:前面提到的房间会得到标识符

doorStock = NORTH | EAST | WEST;

我将这些门存放在一个简单的阵列中:

Door doors[] = new Door[4];

我的问题是:我需要一个能够将标识符映射到数组中正确索引的函数。我并不总是需要4扇门。

我最初做的似乎是最简单的:门数组总是有4个元素,而我不会使用的索引只是保持为空。

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            return doors[0];
        }
        case SOUTH:{
            return doors[1];
        }
        case EAST:{
            return doors[2];
        }
        case WEST:{
            return doors[3];
        }
    }
    return null;
}

为了安全起见,我需要确定我要求的门是否确实存在于房间内。

private boolean doorExists(int doorID){
    return (doorID & doorStock) != 0
}

因此,查询函数如下所示:

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            if(doorExists(NORTH))return doors[0];
            else return null;
        }
        case SOUTH:{
            if(doorExists(NORTH))return doors[1];
            else return null;
        }
        case EAST:{
            if(doorExists(NORTH))return doors[2];
            else return null;
        }
        case WEST:{
            if(doorExists(NORTH))return doors[3];
            else return null;
        }
    }
    return null;
}

哪个有效。但!这样,阵列可能会浪费未使用元素的空间。此外,班级Door可能有任何规模,增加了内存浪费。

更不用说我可能需要更多的“插槽”用于门(例如,如果我尝试在3D中实现这一点),所以我决定尝试根据门的标识符来制作门阵列的大小:

Door doors = new Door[Integer.bitCount(doorStock)];

快速发现IndexOutOfBounds错误。我并不感到惊讶,因为门阵列可以是从0到4的任何大小,所以我需要一种新的哈希方法。

我想出的是两个哈希表,一个用于数组索引:

private final int[][] doorhash = {
    /* NORTH  SOUTH   EAST    WEST doorStock*/
    { -1,     -1,     -1,     -1} /*0000*/,
    { -1,     -1,     -1,      0} /*0001*/,
    { -1,     -1,      0,     -1} /*0010*/,
    { -1,     -1,      0,      1} /*0011*/,
    { -1,      0,     -1,     -1} /*0100*/,
    { -1,      0,     -1,      1} /*0101*/,
    { -1,      0,      1,     -1} /*0110*/,
    { -1,      0,      1,      2} /*0111*/,
    {  0,     -1,     -1,     -1} /*1000*/,
    {  0,     -1,     -1,      1} /*1001*/,
    {  0,     -1,      1,     -1} /*1010*/,
    {  0,     -1,      1,      2} /*1011*/,
    {  0,      1,     -1,     -1} /*1100*/,
    {  0,      1,     -1,      2} /*1101*/,
    {  0,      1,      2,     -1} /*1110*/,
    {  0,      1,      2,      3} /*1111*/
};

和one,这有助于映射上一个表:

private final int[] directionHash = {
    -1, /*0000*/
     3, /*0001 - WEST*/
     2, /*0010 - EAST*/
    -1, /*0011*/
     1, /*0100 - SOUTH*/
    -1, /*0101*/
    -1, /*0110*/
    -1, /*0111*/
     0, /*1000 - NORTH*/
};

所以我当前的映射函数如下所示:

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[NORTH]]];
            else return null;
        }
        case SOUTH:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[SOUTH]]];
            else return null;
        }
        case EAST:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[EAST]]];
            else return null;
        }
        case WEST:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[WEST]]];
            else return null;
        }
    }
    return null;
}

这似乎工作得很好,但我觉得这个问题有一个更简单的解决方案,或者一个浪费较少的哈希表。我觉得这并不像它应该的那样渐渐灵活,或者我过于复杂化了。什么是更好的方法?

感谢您的时间!

7 个答案:

答案 0 :(得分:25)

枚举是你的朋友:

// Each possible direction - with Up/Down added to show extendability.
public enum Dir {
  North,
  South,
  East,
  West,
  Up,
  Down;
}

class Room {
  // The doors.
  EnumSet doors = EnumSet.noneOf(Dir.class);

  // Many other possible constructors.
  public Room ( Dir ... doors) {
    this.doors.addAll(Arrays.asList(doors));
  }

  public boolean doorExists (Dir dir) {
    return doors.contains(dir);
  }
}

让enums为你做繁重的工作是很自然的。他们还提供了非常有效的现成EnumSet

答案 1 :(得分:6)

您的解决方案可能是最节省空间的,但可能是时间效率低下,绝对是最不清楚的。

一个简单的面向对象的方法是拥有Room类,其中包含4个booleans,可以是数组,也可以根据需要单独使用;

public class Room {

    //Consider an ENUM for bonus points
    public static int NORTH=0;
    public static int SOUTH=1;
    public static int EAST=2;
    public static int WEST=3;   

    private boolean[] hasDoor=new boolean[4];

    public Room(boolean northDoor,boolean southDoor,boolean eastDoor,boolean westDoor) {
        setHasDoor(northDoor,NORTH);
        setHasDoor(southDoor,SOUTH);
        setHasDoor(eastDoor,EAST);
        setHasDoor(westDoor,WEST);
    }

    public final  void setHasDoor(boolean directionhasDoor, int direction){
        hasDoor[direction]=directionhasDoor;
    }
    public final boolean  getHasDoor(int direction){
        return hasDoor[direction];
    }


}

任何人都可以清楚地看到这些文档或方法,但这始终是你应该首先瞄准的目标。

然后可以按如下方式使用

public static void main(String[] args){
    ArrayList<Room> rooms=new ArrayList<Room>();

    rooms.add(new Room(true,false,true,false));
    rooms.add(new Room(true,true,true,false));

    System.out.println("Room 0 has door on north side:"+rooms.get(0).getHasDoor(NORTH));

}

或遵循平面图的二维阵列

public static void main(String[] args){
    Room[][] rooms=new  Room[10][10]; 

    rooms[0][0]=new Room(true,false,true,false);
    rooms[0][1]=new Room(true,false,true,false);
    //........
    //........
    //other rooms
    //........
    //........

    System.out.println("Room 0,0 has door on north side:"+rooms[0][0].getHasDoor(NORTH));

}

所有重要的问题是你是否关心以牺牲清晰代码和可能的速度为代价来节省几千字节?在你可能做的记忆匮乏的情况下,否则你不会。

答案 2 :(得分:1)

你绝对是过度设计的。例如,可以通过字符解析并使用char[4]数组来存储(最多)4个不同的字符nswe

您可以使用String收集有关门的信息,每个char将是一扇门(那么您可以使用类似"nnse"的东西,这意味着北墙上有两扇门,1个南门和1个东门)。

您还可以使用ArrayList个字符,如果需要,可以将其转换为数组。  取决于你想要做什么,但有很多方法可以实现你想要做的。

请记住

  

过早优化是万恶之源

答案 3 :(得分:1)

好的,根据评论,我决定不再进一步复杂化。我将使用第一个解决方案,其中doors数组将包含4个元素,映射函数为:

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            if(doorExists(NORTH))return doors[0];
            else return null;
        }
        case SOUTH:{
            if(doorExists(NORTH))return doors[1];
            else return null;
        }
        case EAST:{
            if(doorExists(NORTH))return doors[2];
            else return null;
        }
        case WEST:{
            if(doorExists(NORTH))return doors[3];
            else return null;
        }
    }
    return null;
}

我只是认为这是一个很好的理论问题,就是这样。谢谢你的回复!

答案 4 :(得分:1)

好的,我同意100%的Enums是去这里的方式。我唯一建议的,特别是因为这是逻辑应用于游戏而你可能需要存储定义某个房间的信息,就是使用二进制ID系统。使用这种系统,您可以存储房间可用门的二进制表示。当您处理最多只能有一个项目的项目时,此系统运行良好。在您的情况下,一个房间每个方向只能有一扇门。如果您为房间中的每个门添加所有二进制值,则可以存储该值,然后将该值重新转换回正确的枚举。

示例:

public enum Direction {

    NONE (0, 0),
    NORTH (1, 1),
    SOUTH (2, 2),
    EAST (3, 4),
    WEST (4, 8),
    NORTHEAST (5, 16),
    NORTHWEST (6, 32),
    SOUTHEAST (7, 64),
    SOUTHWEST (8, 128),
    UP (9, 256),
    DOWN (10, 512);

    private Integer id;
    private Integer binaryId;

    private Direction(Integer id, Integer binaryId) {
        this.id = id;
        this.binaryId = binaryId;
    }

    public Integer getId() {
        return id;
    }

    public Integer getBinaryId() {
        return binaryId;
    }

    public static List<Direction> getDirectionsByBinaryIdTotal(Integer binaryIdTotal) {
        List<Direction> directions = new ArrayList<Direction>();

        if (binaryIdTotal >= 512) {
            directions.add(Direction.DOWN);
            binaryIdTotal -= 512;
        }
        if (binaryIdTotal >= 256) {
            directions.add(Direction.UP);
            binaryIdTotal -= 256;
        }
        if (binaryIdTotal >= 128) {
            directions.add(Direction.SOUTHWEST);
            binaryIdTotal -= 128;
        }
        if (binaryIdTotal >= 64) {
            directions.add(Direction.SOUTHEAST);
            binaryIdTotal -= 64;
        }
        if (binaryIdTotal >= 32) {
            directions.add(Direction.NORTHWEST);
            binaryIdTotal -= 32;
        }
        if (binaryIdTotal >= 16) {
            directions.add(Direction.NORTHEAST);
            binaryIdTotal -= 16;
        }
        if (binaryIdTotal >= 8) {
            directions.add(Direction.WEST);
            binaryIdTotal -= 8;
        }
        if (binaryIdTotal >= 4) {
            directions.add(Direction.EAST);
            binaryIdTotal -= 4;
        }
        if (binaryIdTotal >= 2) {
            directions.add(Direction.SOUTH);
            binaryIdTotal -= 2;
        }
        if (binaryIdTotal >= 1) {
            directions.add(Direction.NORTH);
            binaryIdTotal -= 1;
        }

        return directions;
    }

}

答案 5 :(得分:0)

你可以转换它:

public Door getDoor(int doorID){
switch(doorID){
    case NORTH:{
        return doors[0];
    }
    case SOUTH:{
        return doors[1];
    }
    case EAST:{
        return doors[2];
    }
    case WEST:{
        return doors[3];
    }
}
return null;
}

到此:

public Door getDoor(int doorID){
    int index = 0;
    int id = doorID;
    while(id > 1){
        if(id & 1 == 1)
            return null;
        id = id >>> 1;
        index++;
    }
return doors[index];
}

答案 6 :(得分:0)

我甚至会将门模型作为枚举,以便将来可以使用其他类型的门。例如:

public enum Door { TOP, BOTTOM, LEFT, RIGHT };

public class Room {
    private Set<Door> doors = new HashSet<Door>;
    public Room(Door... doors) {
        //Store doors.
        this.doors = doors;
    }

    public boolean hasDoor(Door door) {
        return this.doors.contains(door);
    }
}