2

I would like to check equality between 2 HashMap<Number,String>. My idea is converting each Number into BigDecimal with new BigDecimal(number.toString()) and then check equality with compareTo() == 0 instead of the basic equals that is used when comparing 2 Maps.

Who has a good implementation of this, or even better : a better idea ?

  • It depends how you define if both maps are *equals*. Should they have the same keys only, the same values only, the same key-value pair? – Luiggi Mendoza Apr 30 '13 at 15:25
  • same key-value pair, in my case. –  Apr 30 '13 at 15:25
  • What about `memcmp`? I would test map1.getKeys() against map2.getKeys() and call it equal if they have the same keyset. Then again, maybe you want to test the values too. – Shark Apr 30 '13 at 15:25
  • The problem is : if you have as an entry (int 42, "abcd") and (BigDecimal 42, "abdc") it should be equals –  Apr 30 '13 at 15:28
  • 2
    1: you can't do equals between Numbers. 2 : You should know to never do equals between 2 BigDecimal, since it is based on scale –  Apr 30 '13 at 15:32
  • A bit off tangent, but can you change the design of the `HashMap`? The current design gives you difficulty in handling the comparison between 2 Maps. – nhahtdh Apr 30 '13 at 15:35
  • I was wondering if there is another possibility, but I agree the design complicates things. Unfortunately it is not mine so I can't change a lot. –  Apr 30 '13 at 15:39
  • 1
    @Shark - This is a Java question. There's no such thing as `memcmp` in Java! – Stephen C Apr 30 '13 at 15:53
  • 3
    @Olivier: You have to formalize your definition of "equal" between two maps. What if map 1 contains the mappings [int 42 -> "foo"] and [long 42 -> "bar"] and map 2 contains the mappings [int 42 -> "bar"] and [long 42 -> "map"]. Are the maps equal? There are lot of other corner cases which are neither obvious nor specified by your description. – jarnbjo Apr 30 '13 at 15:58
  • @StephenC `Arrays.equals(byte[], byte[])` ? – Shark Apr 30 '13 at 16:13
  • @Shark - that's nothing like "memcmp". For a start, you can't just cast a reference to a `byte[]`. The type system forbids it. Java does not have "pointers" in the sense that you need for the "memcmp" approach to work. – Stephen C Apr 30 '13 at 23:07

1 Answers1

0

This seems a lot like an X-Y problem; as pointed out in the comments it doesn't make a lot of sense to be comparing arbitrary Number objects; why not just make your maps Map<BigDecimal, String> and just always use BigDecimal directly (normalized with stripTrailingZeros() if necessary)? Then you can just use standard Map equality. Consider stepping back and asking whether you really need the behavior you're describing in the first place.

Notably, Number doesn't implement Comparable, which should be a pretty clear hint that you shouldn't try to compare arbitrary Number instances.


If that isn't an option for some reason, here's a reasonable implementation of the behavior you're describing.

private static BigDecimal toBigDecimal(Number n) {
  return n instanceof BigDecimal ? (BigDecimal)n : new BigDecimal(n.toString());
}

public static <V> boolean numberMapEquality(
    Map<Number, ? extends V> a, Map<Number, ? extends V> b) {
  if (a == b) return true;
  if (a.size() != b.size()) return false;

  // TreeMap uses .compareTo(), not .equals()
  TreeMap<BigDecimal, V> bdMap = new TreeMap<>();
  for (Entry<Number, ? extends V> e : a.entrySet()) {
    bdMap.put(toBigDecimal(e.getKey()), e.getValue());
  }

  if (bdMap.size() != a.size()) {
    // comment out if you don't care about this edge-case - but you should
    throw new IllegalArgumentException(
        "Multiple keys in 'a' normalize to the same value; " +
        "equality comparison is unsafe.");
  }

  // Taken from AbstractMap.equals()
  for (Entry<Number, ? extends V> e : b.entrySet()) {
    BigDecimal bdKey = toBigDecimal(e.getKey());
    V value = e.getValue();
    if (value == null) {
      if (!(bdMap.get(bdKey)==null && bdMap.containsKey(bdKey)))
        return false;
    } else {
      if (!value.equals(bdMap.get(bdKey)))
        return false;
    }
  }
  return true;
}

Alternatively just copy both maps to TreeMap<BigDecimal, V> and call .equals(), but that requires copying both maps, whereas numberMapEquality() only needs to copy one, avoids any copying if they're not the same size, and detects key collisions.

Community
  • 1
  • 1
dimo414
  • 47,227
  • 18
  • 148
  • 244