Question

I have a custom class MarioState that I want to use in a HashMap. The class represents a possible state in a state space of the Mario game. Below is a simplified version of the class MarioState.

In my HashMap I want to store these states. However, not ever property in the MarioState is something that should be considered when comparing two MarioState's. For example if one MarioState has the stuck property set to true and a distance of 30 and another MarioState also has the stuck property set to true but a different distance value (e.g. 20) then they still should be considered the same.

I know for this to work in my HashMap I have to implement the .equals() and .hashcode() methods, which is what I did (by letting them be automatically generated by the InteliJ IDE).

public class MarioState{

    // Tracking the distance Mario has moved.
    private int distance;
    private int lastDistance;

    // To keep track of if Mario is stuck or not.
    private int stuckCount;
    private boolean stuck;

    public MarioState(){
        stuckCount = 0;
        stuck = false;

        distance = 0;
        lastDistance = 0;
    }

    public void update(Environment environment){

        // Computing the distance
        int tempDistance = environment.getEvaluationInfo().distancePassedPhys;
        distance = tempDistance - lastDistance;
        lastDistance = tempDistance;

        // If Mario hasn't moved for over 25 turns then this means he is stuck.
        if(distance == 0){
            stuckCount++;
        } else {
            stuckCount = 0;
            stuck = false;
        }

        if(stuckCount > 25){ stuck = true; }
    }

    public float calculateReward(){
        float reward = 0f;
        reward += distance * 2;
        if(stuck){ reward += -20; }
        return reward;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MarioState that = (MarioState) o;

        if (stuck != that.stuck) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return (stuck ? 1 : 0);
    }  
}

The problem is however that when running the code some of the keys are considered different when it shouldn't be according to their .equals() and .hashcode() functions. What can possibly cause this? Did I forget something?

The code used when inserting states in the HashMap (additional information can be provided if necessary):

public float[] getActionsQValues(MarioState state){
    if(!table.containsKey(state)) {
        float[] initialQvalues = getInitialQvalues(state);
        table.put(state, initialQvalues);
        return initialQvalues;
    }
    return table.get(state);
}

A screenshot when I'm in debug mode shows my table containing two keys with different values, but the keys itself are the same (but in the HashMap it is considered different).

Screenshot of Debug Mode

Était-ce utile?

La solution

Your hash code computation and equality comparison are both based on stuck - but that can change over time.

If you mutate an object after adding it as a key within a hash map, in such a way that the hash code changes, then the key will not be found when you later request it - because the hash code that was stored when the key was first added will no longer be the same as its current hash code.

Wherever possible, try to avoid using mutable objects as keys within a map (even a TreeMap which doesn't use the hash code would have the same problem if you changed the object in a way which would change relative ordering). If you must use mutable objects as keys within a map, you should avoid mutating them after adding them as keys.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top