【问题标题】:Putting a new key into a HashMap replaces an existing different key将新键放入 HashMap 会替换现有的不同键
【发布时间】:2015-10-20 15:11:50
【问题描述】:

答案: 感谢所有的帮助!就像你们大多数人所说的那样,解决方案是创建一个新的 2d int 数组,并从旧数组中复制值。这是我创建的函数,然后我可以将新板添加到 hashmap 中,作为新键。

 void newboard(){
    NewBoard = new int[N][N];
    for(int n = 0; n < N; n++){
        for(int i = 0; i < N; i++){
            NewBoard[n][i] = CurrentBoard[n][i];
        }
    }
}

问题:

我有以下代码,它将一个 3x3 板和一个整数列表,特别是 (f, g, h) 放入一个哈希图中。 Board CurrentBoard 是一个未解的 8 谜题。

public static HashMap<int[][], List<Integer>> map = new HashMap<>();
map.put(CurrentBoard, fgh);

我初始化了 hashmap,对于程序的其余部分,我想遍历 map,以获得我需要的板。移动“0”位置后,地图的每个条目都将是 8 拼图板的特定状态。

这是我的做法。 'cv' 变量只是选择具有最低“f”值的板(键)。

for(Map.Entry<int[][], List<Integer>> mapentry : map.entrySet()) {
        if (cv > mapentry.getValue().get(0)) {
            cv = mapentry.getValue().get(0);
            CurrentBoard = mapentry.getKey();
            fgh = mapentry.getValue();
        }
}

现在我在“CurrentBoard”变量中有一个板,我想将“0”向上移动一行。所以我调用了这个函数:

void moveUp(){
    NewBoard = CurrentBoard;
    memory = NewBoard[ZeroPositionX][ZeroPositionY - 1];
    NewBoard[ZeroPositionY- 1][ZeroPositionX] = 0;
    NewBoard[ZeroPositionY][ZeroPositionX] = memory;
}

然后我做了几个(对于这个问题)不重要的检查,并重新计算这个NewBoardfgh 值。

然后我继续将 NewBoardfgh 值一起放入 hashmap 使用

map.put(NewBoard, fgh);

我的问题是它替换了哈希图中的当前键。换句话说,它不是向 hashmap 添加键和值,而是替换已经存在的键和值。我已尝试打印新版和当前版以确保它们不同。

当我打印hashmapentrySet() 时,它只会给我最新的条目。换句话说,董事会,以及移动“0”后的值。

for(Map.Entry mapentry : map.entrySet()){
        System.out.println(mapentry);
    }

为什么向hashmap 添加新的键和值不起作用?

尾注:我是 Java 新手,所以其中一些可能不是最理想的。如有必要,我会尽力详细解释。

【问题讨论】:

  • 似乎您并没有真正创建一个新数组,而是更改了现有数组中的一些值。然而,这很难说。另请注意,int[][] 既不会覆盖equals 也不会覆盖hashCode,这意味着如果您使用地图中不存在的键(同一实例),您将找不到该值。
  • 你需要克隆你的板子,
  • 除了@fabian 的评论之外,最好将int[][] 包装在一个类中并覆盖hashCode 以使用java.util.Arrays.deepHashCode(yourArray) 之类的东西。对于等号,您可以使用:java.util.Arrays.deepEquals(thisArray, thatArray)

标签: java hashmap


【解决方案1】:

您正在更改 NewBoard 变量/数组的内容,但引用保持不变。因此,当您调用map.put(NewBoard, fgh); 时,键始终是相同的引用(想想内存中的相同地址,尽管每次都有不同的内容)。

如果您想在地图中为每个棋盘状态存储一个新条目,则必须每次都创建一个新数组并复制并更改其内容。

【讨论】:

  • 这确实是问题所在。通过创建一个新数组并简单地将 CurrentBoard 中的值复制到它工作的新数组中。 (而不是简单地设置“NewBoard = CurrentBoard”)正如你所说,我只是一直“指向”我的钥匙,并对该钥匙进行更改,而不仅仅是我将钥匙放入的变量。
【解决方案2】:

问题是您正在修改 int[][] 这是您的 HashMap 键,而不是创建一个新键。这是您的代码摘要(有些行被剪断):

CurrentBoard = mapEntry.getKey(); //here CurrentBoard points at the exact same object as the key in your map
NewBoard = CurrentBoard; //now NewBoard and CurrentBoard are both pointing at the same int[][]
NewBoard[a][b] = 0; //this updates that one object
NewBoard[c][d] = memory; //still updating the same object
map.put(NewBoard, fgh); //NewBoard is still the same object, so this is replacing the existing key

请注意,一般来说,修改 Map 中任何 Key 的内容并不是一个好主意 - 它可能会导致比您看到的更奇怪的行为。

还请注意,使用数组作为映射键通常是错误的 - 因为映射取决于 key1.equals(key2) 的结果,并且对于任何 Java 数组 a1, a2a1.equals(a2) 仅当 a1 == a2 (也就是说,它们是同一个数组)。例如:

int[] a1 = new int[1];
a1[0] = 1;
int[] a2 = new int[1];
a2[0] = 1;
System.out.println(a1.equals(a2)); //prints "false"

【讨论】:

  • 正确。你应该创建一个新板,因为你不能覆盖 int[][] 的等号和哈希码。
【解决方案3】:

更改 int[][] 类型的 Map 键中的值不会改变其 equalshashCode 方法的行为方式。这些是被调用来确定键在语义上是否相同的方法。

因此,要添加新密钥,您要么必须创建一个新密钥,要么使用具有不同 equals/hashCode 逻辑的适当类。

要创建一个新密钥,您可以使用clone()

void moveUp() {
    NewBoard = new int[3][3];
    NewBoard[0] = CurrentBoard[0].clone();
    NewBoard[1] = CurrentBoard[1].clone();
    NewBoard[2] = CurrentBoard[1].clone();
    memory = NewBoard[ZeroPositionX][ZeroPositionY - 1];
    NewBoard[ZeroPositionY- 1][ZeroPositionX] = 0;
    NewBoard[ZeroPositionY][ZeroPositionX] = memory;
}

注意,我们必须调用clone 三次,因为它不能完全克隆一个二维数组(请参阅here)。

更简洁的方法是创建一个像这样的Board 类(未经测试,请原谅拼写错误):

public class Board {

    private int[][] content = new int[3][3];

    /**
     * Creates a new empty board.
     */
    public Board() {
    }

    /**
     * Call this constructor to create a new instance of a given board.
     */
    public Board(Board otherBoard) {
        this.content[0] = otherBoard.content[0].clone()
        this.content[1] = otherBoard.content[1].clone()
        this.content[2] = otherBoard.content[2].clone()
    }

    public void set(int a, int b, int value) {
        content[a][b] = value;
    }

    public int get(int a, int b) {
        return content[a][b];
    }

    public int hashCode() {
        int code = 0;
        for (int a=0; a<3; a++) {
            for (int b=0; b<3; b++) {
                code += content[a][b];
            }
        }
        return code;
    }  

    public boolean equals(Object that) {
        if (!(that instanceof Board)) return false;
        return Arrays.equals(this.content[0], that.content[0])
            && Arrays.equals(this.content[1], that.content[1])
            && Arrays.equals(this.content[2], that.content[2])
    }
}

总的来说,你应该在https://docs.oracle.com/javase/8/docs/api/java/util/Map.html中学习Map的通用合同

此外,在您的代码中,确保您从不更改密钥的内容,一旦它在Map 中使用。否则混乱将统治...

祝你好运。

【讨论】:

    【解决方案4】:

    问题是您正在修改相同的密钥,这将无法按您的预期工作。要获得“正确”的行为,您应该克隆您的密钥。由于您的密钥是int[][],因此您无需执行任何操作,只需致电clone()

        int[][] currentBoard = {{1, 2, 3}, {4, 5, 6}};
    
        HashMap<int[][], String> map = new HashMap<int[][], String>();
    
        map.put(currentBoard, "test1");
    
        int[][] cloned = currentBoard.clone();
    
        cloned[0][0] = 10;
    
        map.put(cloned, "test2");
    
        for(Map.Entry mapentry : map.entrySet()){
            System.out.println(mapentry.getValue());
        }
    

    打印

    test1
    test2
    

    如果你想使用你创建的一些类,你需要覆盖clone(),见Cloneable

    需要注意的是,clone() 只会克隆数组引用,而不是它的值。所以currentBoard[0].equals(cloned[0]) 是真的,但我认为这对你的情况没有影响。

    另请参阅关于克隆Java: recommended solution for deep cloning/copying an instance的这个问题

    【讨论】:

    • 克隆一个二维数组没有用,你需要一个深拷贝。
    • @LouisWasserman 谢谢,但我认为这取决于 OP 的情况,我不确定没有深拷贝是否能解决问题,为此我提出了“重要提示......” /跨度>
    猜你喜欢
    • 2017-08-23
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    • 2021-04-05
    • 2010-12-12
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    相关资源
    最近更新 更多