-1

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)

  • 7
    `int[]s` don't override `equals` and `hashCode`; they compare by identity. – user2357112 Oct 12 '17 at 22:57
  • 5
    Goes for arrays in general. Arrays as keys in maps don't work. Try another data structure. – Joe C Oct 12 '17 at 22:59
  • For the love of `` use collections in preference to arrays. If you change to using `List` instead of `int[]` your problem will vanish. – Bohemian Oct 12 '17 at 23:14

1 Answers1

2

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
Zabuzard
  • 25,064
  • 8
  • 58
  • 82
  • It's practically always preferable to define variables as their most abstract type, ie `List` instead of `ArrayList`. See [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). – Bohemian Oct 12 '17 at 23:22
  • That is correct. I avoided that to keep it simple. However if you want to substitute `int[]` by a `List` it may be preferable to enforce `ArrayList` such that everyone knows **index-based access** works fast, but that's just a side-note for OP. – Zabuzard Oct 12 '17 at 23:23
  • Thank you for taking the time to answer that for me. After realizing its an issue with using an object as a hash key there's definitely duplicate questions like this one. – DillanShmog Oct 13 '17 at 17:57
  • @DillanShmog Your welcome. That's fine, especially if you don't know what the problem is. I mean, how should you search for something that you don't know. – Zabuzard Oct 13 '17 at 22:52