10

While trying to model polynomials, in particular their multiplication, I run into the following problem. During the multiplication, the individual monomials of the two polynomials are multiplied and of course in can happen that I have (3x^2 y + 5x y^2) * (x + y). The result contains 3x^2 y^2 and 5 x^2 y^2, which I want to combine by addition right away.

Naturally I would like to use the part x^2 y^2 of the monomial as a key in a (hash) map to add up the different coefficients (3 and 5 in the example). But the monomial object as I envisage it should naturally also contain the coefficient, which should not be part of the map key.

Of course I could write equals/hashcode of the monomial object such that they ignore the coefficient. But this feels just so wrong, because mathematically a monomial clearly is only equal to another one if also the coefficients are equal.

Introducing a coefficient-free monomial object for intermediate operations does also not look right.

Instead of using the map, I could use a list and use a binary search with a dedicated comparator that ignores the coefficient.

Short of implementing a map which does not use the keys' equals/hashcode, but a dedicated one, are there any better ideas of how to fuse the monomials?

Harald
  • 4,575
  • 5
  • 33
  • 72

4 Answers4

5

Since the JDK implementation of [Linked]HashMap does not permits you to override the equals/hashCode implementation, the only other ways are:

  • a wrapping object like this:

      class A {
        private final String fieldA; // equals/hashCode based on that field.
        private final String fieldB; // equals/hashCode based on that field.
      }
    
      class B {
        private A a;
        public int hashCode() {return a.fieldA.hashCode();} 
        public boolean equals(Object o) {... the same ... }
      }
    
      Map<B, Value> map = new HashMap<B, Value>();
      map.put(new B(new A("fieldA", "fieldB")), new Value(0));
    

    Well, with more getters/constructors.

    This can be annoying, and perhaps there exists some library (like Guava) that allows an equals/hashCode method to be given like you can give a Comparator to TreeMap.

    You'll find below a sample implementation that point out what to do to decorate an existing map.

  • use a TreeMap with a specific Comparator. The other answer point it, but I'd say you'll need to correctly define a Comparator because this could lead to problems: if you compareTo method returns 0 when equality is reached, and 1 in other case, this means there is no natural ordering. You should try to find one, or use the wrapper object.


If you want to take the challenge, you can create a basic implementation using delegation/decoration over another HashMap (this could be another kind of map, like LinkedHashMap):

public class DelegatingHashMap<K,V> implements Map<K,V> {
  private final BiPredicate<K,Object> equalsHandler;
  private final IntFunction<K> hashCodeHandler;
  private final Map<Wrapper<K>,V> impl = new HashMap<>();

  public DelegatingHashMap(
    BiPredicate<K,Object> equalsHandler,
    IntFunction<K> hashCodeHandler
  ) {
    this.equalsHandler = requireNonNull(equalsHandler, "equalsHandler");
    this.hashCodeHandler= requireNonNull(hashCodeHandler, "hashCodeHandler");
  }

  public Object get(K key) {
    Wrapper<K> wrap = new Wrapper<>(key);
    return impl.get(wrap);
  }  

  ...

  static class Wrapper<K2> {
    private final K2 key;
    private final BiPredicate<K> equalsHandler;
    private final IntFunction<K> hashCodeHandler;
    public int hashCode() {return hashCodeHandler.apply(key);}
    public boolean equals(Object o) {
      return equalsHandler.test(key, o);
    }
  }
}

And the code using the map:

DelegatingHashMap<String, Integer> map = new DelegatingHashMap<>(
  (key, old) -> key.equalsIgnoreCase(Objects.toString(o, "")),
  key -> key.toLowerCase().hashCode()
);
map.put("Foobar", 1);
map.put("foobar", 2);

System.out.println(map); // print {foobar: 2}

But perhaps the best (for the memory) would be to rewrite the HashMap to directly use the handler instead of a wrapper.

NoDataFound
  • 11,381
  • 33
  • 59
  • Using a Wrapper is of course a possibility, but the I consider this too much overhead, if not for the performance (don't optimize early), but conceptually it is too much in my opinion. – Harald Sep 12 '14 at 19:06
  • Then try with a TreeMap, but this should be a TreeMap> or you should not forget to sum the coefficient :) – NoDataFound Sep 12 '14 at 20:33
3

You could use a TreeMap with a custom comparator:

TreeMap(Comparator<? super K> comparator)

Constructs a new, empty tree map, ordered according to the given comparator.

(Source)

Alexander Langer
  • 2,883
  • 16
  • 18
3

Consider using a TreeMap, which is a SortedMapand thus also a Map. You can provide a Comparator to its constructor. The sorted map will use that Comparator for sorting the map keys. But importantly, for your case, it will consuder keys to be equal if the Comparator returns 0. In your case that will require a Comparator that is not consustent with equals, which could cause you problems if you are not careful.

Another option is to introduce another class, which acts as an adaptor for a Mononomial and can be used as a map key having the properties you deserve.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
  • The TreeMap relies on binary sorting the Monomial. While it is good that I can provide the needed comparator, I would then rather use just a sorted binary array. I think the overhead smaller and the performance should be comparable. – Harald Sep 12 '14 at 19:03
1

I think it may be better to separate the monomial into 2 parts: the coefficient and the variable. That way you can use the variable part in your map as the key and the coefficient as the value (which can then up updated).

All this code should be implementation details inside a Polynomial object

I'm not sure why you think a coefficient-free monomial does not look right. You don't have to expose the object to the outside if you don't want. But it might be a nice way to have getters on your Polynomial to get the coefficients for each monomial.

dkatzel
  • 31,188
  • 3
  • 63
  • 67
  • Well, the monomial looks wrong without the coefficient, because without it, it is no longer a monomial in the mathematical sense. – Harald Sep 12 '14 at 19:01