So I'm solving Day 24 of Advent of Code and I run into a strange problem with Java and/or Eclipse (most likely Eclipse). My algorithm is correct but it doesn't return the correct result (I know this because I've, since, submitted a correct solution). It appears that my HashMap isn't working properly--I know this is a problem with hashCode() (because I've since, quite accidentally, fixed it).
Here is the original (eclipse generated) hashCode() method:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (evenRow ? 1231 : 1237);
result = prime * result + x;
result = prime * result + y;
return result;
}
So here is my thought: x
and y
need to be "bigger" so I try long
's. I do this and get wildly different (incorrect) results (with an updated, generated by eclipse, hashCode() method). I do a bunch of other stuff, including changing the values to BigInteger...none of which makes sense because there's no way I should need that much precision (for this particular problem). Having said that, the BigInteger implementation gives the same result as the int
implementation.
Further confusion arises when I try to manually create a hashCode() method, like so:
public int hashCode(){
return x + y;
}
Not great but it should (theoretically) work, right? If the two objects are equal, then x + y
should be equal then it will check the equals(...) method for equality (which I'm virtually certain is correctly implemented via eclipse). This returns yet even different results.
Changing the hashCode() method changes the results I get. There are two reasons I can think of for getting these disparate effects:
- hashCode() is clearly returning collisions--I don't see why this would be a problem as long as hashCode() is consistent with equals(...) which I think eclipse's implementation satisfies.
- I'm using a mutable object as a key (I suspect this is the culprit). So instead of creating multiple new object keys, I have a single object that I change as needed for lookup.
I don't see why the 2nd should be a problem because the contract of hashCode()
is:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
But provided no information used in equals comparisons on the object is modified.
? If I change any field member, it has been modified so should not be expected to return the same result!
So what I don't understand: is why is the hashCode() method interfering with the proper function of a HashMap. I mean don't get me wrong--if the two hashCodes for the same object aren't equal, that's going to be a problem, but I don't understand why two same hashCode for differing objects (via equals(...)
) should create a problem.
If it helps, this is the equals(...)
method generated by eclipse:
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HexCoord other = (HexCoord) obj;
if (evenRow != other.evenRow)
return false;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
Edit: For what it's worth, changing the hashCode() function to this (which I did quite randomly) "solves" the problem:
public int hashCode() {
final int prime = 341; // <-- changed 31 to 341
int result = 1;
result = prime * result + (evenRow ? 1231 : 1237);
result = prime * result + x;
result = prime * result + y;
return result;
}
Further Edit: added code example: (I really think this is going to introduce un-useful questions...I've questioned whether or not my methods here are correct--and they appear to be so because I've gotten a correct result--it's the HashMap getting and putting of objects that seems to be the problem)
Some Explanation:
Here is coord.set
(so it doesn't just copy the actual object--it recreates it):
public void set(final HexCoord coord) {
this.x = coord.x; // <-- these are int's
this.y = coord.y;
this.evenRow = coord.evenRow;// <-- this is a boolean
}
Here is the getHexTile(...)
method:
private static HexTile getHexTile(Map<HexCoord, HexTile> tiles,
HexCoord coord) {
HexTile tile = tiles.get(coord);
if (tile == null) {
tile = new HexTile(coord);
tiles.put(coord, tile);
}
return tile;
}
with new HexTile(coord)
:
public HexTile(final HexCoord coord) {
this.coord = coord.copy();
}
and copy
:
public HexCoord copy() {
final HexCoord copy = new HexCoord();
copy.set(this); // <-- rights the values to the new copy
return copy;
}
// set black count
tiles.values().stream()
.filter(tile -> tile.getColor() == HexTile.Color.black)
.collect(Collectors.toList()).stream().forEach(tile -> {
coord.set(tile.coord);
coord.moveE();
HexTile neighbor = getHexTile(tiles, coord);
neighbor.addBlackNeighbor();
coord.moveW();
coord.moveW();
neighbor = getHexTile(tiles, coord);
neighbor.addBlackNeighbor();
coord.moveE();
coord.moveNE();
neighbor = getHexTile(tiles, coord);
neighbor.addBlackNeighbor();
coord.moveSW();
coord.moveSW();
neighbor = getHexTile(tiles, coord);
neighbor.addBlackNeighbor();
coord.moveNE();
coord.moveNW();
neighbor = getHexTile(tiles, coord);
neighbor.addBlackNeighbor();
coord.moveSE();
coord.moveSE();
neighbor = getHexTile(tiles, coord);
neighbor.addBlackNeighbor();
});
tiles.values().parallelStream().forEach(tile -> {
final int blackCount = tile.getBlackCount();
if (tile.getColor() == HexTile.Color.black) {
if (blackCount == 0 || blackCount > 2) {
tile.flip();
}
} else if (blackCount == 2) {
tile.flip();
}
tile.resetCount();
});
Resolution: For anyone still interested the fix was simple: I was already cloning the HexCoord
object when I created the HexTile
. The "key" (get it, key...as in key-value pair), was making sure that the Map
held distinct keys by making sure that the map's keys came from the newly created HexTile
:
private static HexTile getHexTile(Map<HexCoord, HexTile> tiles,
HexCoord coord) {
HexTile tile = tiles.get(coord);
if (tile == null) {
tile = new HexTile(coord);
tiles.put(tile.coord, tile);
}
return tile;
}