1

equals我有一个类和不同的等价规则(和的不同实现hashCode)。数据首先在一个进程中生成,在该进程中应用一个等价规则,然后将数据馈送到第二个进程,在该进程中应用另一个等价规则。特别是,我正在做很多map操作,equals并且hashCode被标准库隐式调用(我无法控制)。您认为实现这一目标的最佳方法是什么?我现在有两个解决方案:

  1. equals用不同的和定义两个子类hashCode。在过程1之后,通过启动另一个子类的对象来进行转换。
  2. 在类中引入可变状态以指示应用哪个等价规则。

那么您认为哪一个更好,或者还有其他好的解决方案吗?

4

4 回答 4

4

一个可能更优雅的解决方案是Map允许自定义散列和相等评估的自定义类。

trait MappingScheme[KEY_CLASS,VALUE_CLASS] implements Comparable[VALUE_CLASS] {
    def generateHash(key: KEY_CLASS): Int
    // Also imposes compare() definition from Comparator
}

class CustomSchemeMap[K,V](mappingScheme: MappingScheme[K,V]) implements Map[K,V] {
    // Implement Map methods; use mappingScheme to generate hashes and
    // perform equality checks
}

在您的场景中,您将创建两个自定义MappingSchemes 并在您CustomSchemeMap的 s. 这种方法比您建议的解决方案性能更高(没有额外的实例创建,您不必改变您的对象),但它也更符合逻辑并且更容易遵循。


但是,实现 aMap可能是一项艰巨的任务。如果这似乎遥不可及,我会创建简单的适配器类来包裹您的对象并将它们输入到地图中。

class KeyableAdapter1(o: OriginalClass) {
    override def hashCode() = o.hashCode + 10 // e.g.
    override def equals(that: Object) = o.stuff == that.stuff // e.g., after cast
    def get(): OriginalClass = o // To get it back out, if you need to
}

class KeyableAdapter2(o: OriginalClass) {
    override def hashCode() = o.hashCode ^ 10
    override def equals(that: Object) = o.otherStuff = that.otherStuff
    def get(): OriginalClass = o
}

// Later
myMap.put(new KeyableAdapter1(o1), stuff)
myOtherMap.put(new KeyableAdapter2(o1), moreStuff)

这类似于子类化方法,不同之处在于您可以通过 取回原始对象get(),并且更容易遵循(至少在我看来)。

于 2013-01-20T01:50:26.080 回答
1

用不同的equals和hashCode定义两个子类。在过程1之后,通过启动另一个子类的对象来进行转换。

这是正确的,但我认为这两个类在语义上没有区别。尽管它们代表相同,但它们只会在另一种情况下使用。

在类中引入可变状态以指示应用哪个等价规则。

永远不要这样做,这是坏的:

  • 如果你在全局范围内更改状态,这是一个很大的魔法,这可能会导致很多问题,尤其是如果你在更多线程中使用这些类。你可以破坏一些现有的地图等等。
  • 如果您在本地更改它,它就不那么神奇了,但是您几乎肯定会违反 equals 和 hashCode 合同中的对称性,即对于所有对象 o1 和 2o,都o1.equals(o2)暗示o2.equals(o1). 您可以比较比较器(例如[1]),它至少会保持合同。虽然它守约,但它是丑陋的。

[1]

def equals(o: Object) = o match {
    case that: MyClass => 
        (that.comparator == this.comparator) && comparator.compare(this, that)
    case _ => false // for null values and other classes
}
于 2013-01-20T07:13:33.887 回答
0

最后我发现编写我自己的自定义Map是要走的路(至少在我的问题中)。在我研究了 scala 标准库一段时间后,我发现它非常容易。无论是否可变,其中的元素相等和 hashCode 方法HashMap都继承自HashTableHashTable.Utils受到保护,这意味着任何子类都可以轻松覆盖它。所以以下是我最终的结果:

trait Equility[T] {
  def equal(t1: T, t2: T): Boolean
  def hash(t: T): Int
}

class MapWithEquility[K, V](e: Equility[K]) extends scala.collection.mutable.HashMap[K, V] {
  override def elemHashCode(key: K) = e.hash(key)
  override def elemEquals(key1: K, key2: K) = e.equal(key1, key2)
}

我做了一个简单的测试,效果很好。

于 2013-01-25T18:54:49.837 回答
0

这是对@cheeken 提出的第一个解决方案的改进。我强烈建议不要采用第二种,除非你正在做一个琐碎的项目。使用第二种方法,您不能强制您放入地图中的所有项目都使用相同的哈希计算它们的哈希,这可能会导致错误和意外的行为,这在运行时很难解释。

正确的方法是从 Scala 库中的 HashMap 中获取灵感:

@SerialVersionUID(2L)
class HashMap[A, +B] extends Map[A,B] with MapLike[A, B, HashMap[A, B]] with Serializable with CustomParallelizable[(A, B), ParHashMap[A, B]] {

  override def size: Int = 0

  override def empty = HashMap.empty[A, B]

  def iterator: Iterator[(A,B)] = Iterator.empty

  override def foreach[U](f: ((A, B)) =>  U): Unit = { }

  def get(key: A): Option[B] =
    get0(key, computeHash(key), 0)

  override def updated [B1 >: B] (key: A, value: B1): HashMap[A, B1] =
    updated0(key, computeHash(key), 0, value, null, null)

  override def + [B1 >: B] (kv: (A, B1)): HashMap[A, B1] =
    updated0(kv._1, computeHash(kv._1), 0, kv._2, kv, null)

  override def + [B1 >: B] (elem1: (A, B1), elem2: (A, B1), elems: (A, B1) *): HashMap[A, B1] =
    this + elem1 + elem2 ++ elems
    // TODO: optimize (might be able to use mutable updates)

  def - (key: A): HashMap[A, B] =
    removed0(key, computeHash(key), 0)

  protected def elemHashCode(key: A) = key.##

  protected final def improve(hcode: Int) = {
    var h: Int = hcode + ~(hcode << 9)
    h = h ^ (h >>> 14)
    h = h + (h << 4)
    h ^ (h >>> 10)
  }

  private[collection] def computeHash(key: A) = improve(elemHashCode(key))

  protected type Merger[B1] = ((A, B1), (A, B1)) => (A, B1)

  private[collection] def get0(key: A, hash: Int, level: Int): Option[B] = None

  private[collection] def updated0[B1 >: B](key: A, hash: Int, level: Int, value: B1, kv: (A, B1), merger: Merger[B1]): HashMap[A, B1] = 
    new HashMap.HashMap1(key, hash, value, kv)

  protected def removed0(key: A, hash: Int, level: Int): HashMap[A, B] = this

  protected def writeReplace(): AnyRef = new HashMap.SerializationProxy(this)

  def split: Seq[HashMap[A, B]] = Seq(this)

  def merge[B1 >: B](that: HashMap[A, B1], merger: Merger[B1] = null): HashMap[A, B1] = merge0(that, 0, merger)

  protected def merge0[B1 >: B](that: HashMap[A, B1], level: Int, merger: Merger[B1]): HashMap[A, B1] = that

  override def par = ParHashMap.fromTrie(this)

}

如果你看,你可以只写下面的类:

class CustomHashMap[A,+B](val hashCalculator:HashCalculator[A]) extends HashMap[A,B] {
    //protected def elemHashCode(key: A) = key.## 
    override def elemHashCode(key: A) = hashCalculator(key)
}

您必须确保所有公共方法的行为正确,包括 par (您需要实现使用特殊哈希器的并行哈希映射)和合并,以及不应该返回 HashMap.empty[A,B]CustomHashMap.empty[A,B]

于 2013-01-20T09:34:19.517 回答