reiko1879
reiko1879

Reputation: 37

How to change the value of a key in hashmap

I have a project where I need to convert the value of x into 1+5 and calculate it to 6.

I have no problem if x is the only value that needs to be converted, but if I have (5*x) it becomes an issue. Rather than doing 5*(1+5) and calculate it to 30, it simply tries to compute 5*x and this results to an exception that says it cannot compute because a variable is present.

The ExpFoo class is an interface and it's the superclass of TimesExpFoo, PlusFoo, IntFoo and VarFoo. Replacement class is completely separated.

TimesExpFoo multiplies the number and PlusFoo adds the numbers.

Here's the Main class. This gets the varfoo and numbers. The Replacement class puts the varfoo and numbers in a hashmap that's used by the Replacement class. The varfoo is the key and the numbers are the values.

    ExpFoo x = new VarFoo("x");
    ExpFoo e1 = new IntFoo(1);
    ExpFoo e2 = new IntFoo(2);
    ExpFoo e5 = new IntFoo(5);
    ExpFoo times = new TimesExpFoo(e5, x);
    Replacement s = new Replacement();
    s.put(new VarFoo("x"), new PlusExpFoo(e1, e5));
    times.computeValue(s);
    System.out.println(times.computeValue(s));

It will then go to ExpFoo class using computeValue method, the subst contains [x:=1+5], it will first check the applyReplacement method:

    default int computeValue(Replacement subst) {
       ExpFoo specialised = applyReplacement(subst);
       return specialised.computeValue();
    }

It will then go to TimesExpFoo class using applyReplacement method, it will return [x:=1+5]:

@Override
   public ExpFoo applyReplacement(Replacement s) {
    return this;
   }

It will go to back ExpFoo class, this time specialised has (5*x) and subst has [x:=1+5], it will unfortunately return specialised value which is (5*x):

   default int computeValue(Replacement subst) {
    ExpFoo specialised = applyReplacement(subst);
    return specialised.computeValue();
}

It will then go to TimesExpFoo's computeValue method, the getLeft method contains 5 and getRight method contains x:

    @Override
    public int computeValue() {
    return getLeft().computeValue() * getRight().computeValue();
}

Which will finally go to the VarFoo class and use the computeValue method which will throw an error that it cannot compute because of a variable present.

I'm aware that TimesExpFoo's applyReplacement method returns the value of subst itself and I could do more with it but I'm not sure how it works. I've tried using return s.get((VarFoo) getRight()) but it will just give me a casting error TimesExpFoo cannot be cast to class VarFoo.

My VarFoo class overrides the equals and hashcodes methods. If the varfoo, x, has the same hashcode and it should overwrite 5*x with 5*(1+5). It has no problem doing it on its own.

I'm puzzled by the fact that it wouldn't overwrite the x variable. Does it has something to do with the hashmap?

My applyReplacement method is just a signature in the Expression class, so I doubt that's the issue.

Here's the Replacement class which uses hashmap:

public class Replacement {

private Map<VarFoo, ExpFoo> replacementMap;

public Replacement() {
    replacementMap = new HashMap<>();
}

public ExpFoo put(VarFoo var, ExpFoo exp) {
    if(replacementMap.containsKey(null) || replacementMap.containsValue(null)){
        throw new NullPointerException();
    }
    return replacementMap.put(var, exp);
}

public boolean forget(VarFoo var) {
    if(replacementMap.containsKey(null)) {
        throw new NullPointerException();
    }
    else {
        if(!replacementMap.containsKey(var))
        return true;
    }
    return false;
}

public ExpFoo get(VarFoo var) {
    if(replacementMap.containsKey(null)){
        throw new NullPointerException();
    }
    return replacementMap.get(var);
}

public boolean hasMappingFor(VarFoo var) {
    if(replacementMap.containsKey(var)){
        return true;
    }
    else if(replacementMap.containsKey(null)){
        throw new NullPointerException();
    }
    return false;
}

}

My VarFoo's equals and hashcode methods, it uses the instance variable name which is a String:

    @Override
public boolean equals(Object o) {
    if (o == null) return false;
    if (!(o instanceof VarFoo))
        return false;
    if (o == this)
        return true;
    return name.equals(((VarFoo) o).name);
}

@Override
public int hashCode() {
    int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

The TimesExpFoo class is a subclass of BinaryExpFoo which is an abstract class and it's the subclass of ExpFoo. The BinaryExpFoo is the one that gives the left, right and operatorSymbol instance variables.

public class TimesExpFoo extends BinaryExpFoo {

public TimesExpFoo(ExpFoo left, ExpFoo right) {
    super(left, right, "*");
}

@Override
public int computeValue() {
    return getLeft().computeValue() * getRight().computeValue();
}

@Override
public ExpFoo applyReplacement(Replacement s) {
    return this;
}

@Override
public boolean equals(Object o) {
    if (!(o instanceof TimesExpFoo)) {
        return false;
    }
    return super.equals(o);
}

@Override
public int hashCode() {
    return super.hashCode();
}

Upvotes: 0

Views: 96

Answers (2)

Progman
Progman

Reputation: 19546

The goal of your applyReplacement() method is to replace all the variables by their real values/expressions. The only expression which can do that is obviously the VarFoo class. Its method might look like this:

@Override
public ExpFoo applyReplacement(Replacement s) {
    ExpFoo exp = s.get(this);
    exp = exp.applyReplacement(s); // replace any contained variables as well
    return exp;
}

Similar to the computeValue() method, the applyReplacement() method has to be implemented recursively for classes which contain other ExpFoo objects. These "inner" expressions has to be "transformed" as well, where the variables are being replaced. As an example, for the TimesExpFoo class the method might look like this:

@Override
public ExpFoo applyReplacement(Replacement s) {
    return new TimesExpFoo(
        this.left.applyReplacement(s),
        this.right.applyReplacement(s)
    );
}

This way the TimesExpFoo object gets translated to a similar TimesExpFoo object without any variables anymore. Then you can use your computeValue() method since everything has been replaced.

Your actual implementation has to check for chained variable replacements. Imagine you have the expressions:

x = 5
y = 4+x
z = 3+y

You might need some kind of loops or recursive calls (like shown above) so the expression z will finally be transformed into 3+4+5 and not just stop at 3+y.

Upvotes: 0

fabfas
fabfas

Reputation: 2228

The HashMap uses hashCode and equals to check if the key already present in the map. If the key already exists the old value is replaced with a new one. The key itself remains unmodified.

If you want to use instances of the VarFoo class as HashMap keys, you have to override the hashCode() method so that it adheres to the contract: Equal objects return the same hashCode.

One common way is to let our IDE generate the equals() and hashCode() method for your VarFoo class

Upvotes: 1

Related Questions