HashMap keys return null when the map demonstrably contains them [duplicate]

孤街浪徒 提交于 2019-12-13 08:56:19

问题


I'm currently trying to solve a code wars problem titled Battle ships: Sunk damaged or not touched?. Given a 2D array containing 'ships' and another 2D array containing the attack coordinates I have to generate a score. I populate a hashmap with the ship locations and then check the attack locations against the map. When printed, the original locations and attacks are identical on the first test case. Despite this, map.get() continues to return null and map.containsKey() returns false.

public class BattleshipsSDN {
public static Map<String,Double> damagedOrSunk(final int[][] board, final int[][] attacks) {

    int y;
    HashMap<int[], Integer> ships = new HashMap<>();

    // build map of boat locations
    for (int i = 0; i < board.length; i++) {
        y = board.length - i;
        for (int j = 0; j < board[i].length; j++) {
            if (board[i][j] == 0) continue;
            else {
                int[] location = {j+1,y};
                ships.put(location, board[i][j]);
                System.out.println("Location: "+location[0]+","+location[1]);
                //System.out.println("Value: "+ships.get(location));
            }
        }
    }

    //establish original boat lengths
    int ship1 = Collections.frequency(new ArrayList<Integer>(ships.values()), 1);
    int ship2 = Collections.frequency(new ArrayList<Integer>(ships.values()), 2);
    int ship3 = Collections.frequency(new ArrayList<Integer>(ships.values()), 3);
    System.out.println("Ships: "+ship1+ship2+ship3);

    for(int[] x : ships.keySet()) System.out.println(x[0]+","+x[1]);

    //check for hits
    for (int[] x : attacks) {
        System.out.println(x[0]+","+x[1]);
        if (ships.get(x) == null) continue;
        else{
            System.out.println("Hit");
            ships.remove(x);
        }
    }

    double sunk = 0;
    double hit = 0;
    double missed = 0;

    //find number of ship spots after attacks
    int leftShip1 = Collections.frequency(new ArrayList<Integer>(ships.values()), 1);
    int leftShip2 = Collections.frequency(new ArrayList<Integer>(ships.values()), 2);
    int leftShip3 = Collections.frequency(new ArrayList<Integer>(ships.values()), 3);
    System.out.println("Ships: "+leftShip1);

    if (ship1 > 0) {
        if (leftShip1 == 0) sunk++;
        else if (ship1 % leftShip1 > 0) hit++;
        else if (ship1 == leftShip1) missed++;
    }
    if (ship2 > 0) {
        if (leftShip2 == 0) sunk++;
        else if (ship2 % leftShip2 > 0) hit++;
        else if (ship2 == leftShip2) missed++;
    }
    if (ship3 > 0) {
        if (leftShip3 == 0) sunk++;
        else if (ship3 % leftShip3 > 0) hit ++;
        else if (ship3 == leftShip3) missed++;
    }
    HashMap<String, Double> score = new HashMap<>();
    score.put("sunk", sunk);
    score.put("damaged", hit);
    score.put("notTouched", missed);
    score.put("points", sunk + hit/2 - missed);
    return score;

}

}

I'm not asking you to solve the problem for me. I'm just completely stumped as to why my HashMap is acting this way. Which likely means it's some really small stupid thing.

Note: The y values of the locations are flipped because in the problems 'board' the y-coord is measured from the bottom. So in a 4x4 board or array the index [0][0] corresponds to the coordinates (1,4)


回答1:


Explanation

The problem is that you are using int[] as key for your HashMap. Arrays don't override the methods equals and hashCode. Thus those methods, for them, compares objects by their identity instead of their content.

Consider this:

int[] first = new int[] { 1, 2, 3 };
int[] second = new int[] { 1, 2, 3 };

System.out.println(first.equals(second)); // Prints 'false'

Both arrays have the same content but they are considered not equal since they are different objects (first != second).

When you now call something like map.get(key) the map searches for the key by using its hash-code, the one returned by the hashCode method. However this method also works on an identity-base for arrays.

If you now store the data with a key and later re-create a key with the same content, in order to fetch the data, you will not find it anymore:

Map<int[], String> map = new HashMap<>();

// Put data
int[] key = new int[] { 1, 2, 3 };
map.put(key, "test");

// Retrieve it
int[] similarKey = new int[] { 1, 2, 3 };
String data = map.get(similarKey); // Is 'null', not ' test'

// Try it with 'key' instead of 'similarKey'
String otherData = map.get(key); // Works now since same object

Though similarKey has the same content, it has a different hashCode since it is not the same object (by identity).


Solution

To solve the issue simply use a data-structure which implements hashCode and equals not per identity but per comparing the content. You may use stuff from Collection (documentation), an ArrayList (documentation) for example:

Map<List<Integer>, String> map = new HashMap<>();

// Put data
int[] key = new int[] { 1, 2, 3 };
List<Integer> keyAsList = new ArrayList<>(Arrays.asList(key));
map.put(keyAsList, "test");

// Retrieve it
int[] similarKey = new int[] { 1, 2, 3 };
List<Integer> similarKeyAsList = new ArrayList<>(Arrays.asList(similarKey));
String data = map.get(similarKeyAsList); // Is 'test' now


来源:https://stackoverflow.com/questions/46720073/hashmap-keys-return-null-when-the-map-demonstrably-contains-them

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!