Map,允许分别提供等值比较器和散列函数

Map which allows to provide the equals-comparator and the hashing function separately

提问人:Harald 提问时间:9/11/2014 更新时间:4/7/2021 访问量:11325

问:

在尝试对多项式进行建模时,特别是它们的乘法,我遇到了以下问题。在乘法过程中,两个多项式的单个单项式相乘,当然可以发生我有 (3x^2 y + 5x y^2) * (x + y)。结果包含 3x^2 y^2 和 5 x^2 y^2,我想立即通过加法将它们组合起来。

当然,我想使用单项式的部分 x^2 y^2 作为(哈希)映射中的键,以将不同的系数相加(示例中的 3 和 5)。但是,我设想的单项式对象自然也应该包含系数,这不应该是映射键的一部分。

当然,我可以编写单项式对象的等于/哈希码,以便它们忽略系数。但这感觉太不对劲了,因为从数学上讲,只有当系数也相等时,单项式显然等于另一个单项式。

为中间运算引入无系数单项式对象看起来也不对。

我可以使用列表,而不是使用地图,并使用带有忽略系数的专用比较器的二值搜索。

除了实现一个不使用键的等于/哈希码的映射,而是一个专用的映射之外,还有更好的想法来融合单项式吗?

Java 哈希图 符号数学 多项式

评论


答:

5赞 NoDataFound 9/11/2014 #1

由于 的 JDK 实现不允许您覆盖 / 实现,因此唯一的其他方法是:[Linked]HashMapequalshashCode

  • 像这样包装对象:

      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));
    

    好吧,有更多的 getter/constructors。

    这可能很烦人,也许存在一些库(如 Guava)允许给出 equals/hashCode 方法,就像您可以给 .ComparatorTreeMap

    您将在下面找到一个示例实现,该实现指出了如何装饰现有地图。

  • 将 a 与特定的 .另一个答案指出了这一点,但我想说你需要正确定义 a,因为这可能会导致问题:如果你的方法在达到相等时返回 0,而在其他情况下返回 1,这意味着没有自然排序。您应该尝试找到一个,或者使用包装器对象。TreeMapComparatorComparatorcompareTo


如果你想接受挑战,你可以使用委托/装饰来创建一个基本的实现(这可能是另一种地图,比如):HashMapLinkedHashMap

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);
    }
  }
}

以及使用地图的代码:

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}

但也许最好的(对于内存)是重写 直接使用处理程序而不是包装器。HashMap

评论

0赞 Harald 9/13/2014
使用包装器当然是一种可能性,但我认为这太过分了,如果不是为了性能(不要及早优化),但从概念上讲,在我看来这太多了。
0赞 NoDataFound 9/13/2014
然后尝试使用 TreeMap,但这应该是 TreeMap<K, List<V>>或者您不应该忘记对系数求和:)
3赞 Alexander Langer 9/11/2014 #2

您可以将 a 与自定义比较器一起使用:TreeMap

TreeMap(Comparator<? super K> comparator)

构造一个新的空树状图,根据给定的比较器进行排序。

()

1赞 dkatzel 9/11/2014 #3

我认为最好将单项式分为两部分:系数和变量。这样,您就可以将地图中的变量部分用作键,将系数用作值(然后可以更新)。

所有这些代码都应该是对象内部的实现细节Polynomial

我不确定为什么你认为无系数单项式看起来不对。如果您不想,则不必将对象暴露在外部。但是,在 getter 上设置吸气剂以获得每个单项式的系数可能是一种很好的方法。Polynomial

评论

0赞 Harald 9/13/2014
好吧,没有系数的单项式看起来是错误的,因为没有系数,它就不再是数学意义上的单项式。
3赞 Raedwald 9/11/2014 #4

考虑使用 ,它是 ,因此也是 .您可以为其构造函数提供 a。排序后的映射将使用它对映射键进行排序。但重要的是,对于您的情况,如果返回 0,则密钥将相等。在你的情况下,这将需要一个与等号不一致的,如果你不小心,这可能会给你带来问题。TreeMapSortedMapMapComparatorComparatorComparatorComparator

另一种选择是引入另一个类,该类充当 a 的适配器,并可用作具有您应得的属性的映射键。Mononomial

评论

0赞 Harald 9/13/2014
TreeMap 依赖于对单项式进行二进制排序。虽然我可以提供所需的比较器是件好事,但我宁愿只使用排序的二进制数组。我认为开销更小,性能应该相当。