1

クラスとそれに異なる同等性ルールがあります(との異なる実装equalshashCode。データは、最初に1つの同等性ルールが適用される1つのプロセスで生成され、次に他の同等性ルールが適用される2番目のプロセスに送られます。特に、私は多くのmap操作を行ってequalsおりhashCode、標準ライブラリ(私は制御できません)によって暗黙的に呼び出されます。これを達成するための最良の方法は何だと思いますか?私は今2つの解決策を持っています:

  1. equalsとが異なる2つのサブクラスを定義し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
}

シナリオでは、2つMappingSchemeのカスタムを作成し、それらを適切に使用しますCustomSchemeMap。このアプローチは、提案するソリューションよりもパフォーマンスが高くなります(追加のインスタンスを作成する必要がなく、オブジェクトを変更する必要もありません)が、論理的に意味があり、従うのも簡単です。


ただし、を実装するのMapは大変な作業になる可能性があります。それが手の届かないように思われる場合は、オブジェクトをラップしてマップにフィードするための単純なアダプタクラスを作成します。

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を使用して2つのサブクラスを定義します。プロセス1の後、他のサブクラスのオブジェクトを開始して変換を実行します。

これは正しいですが、これら2つのクラスは意味的に違いはないと思います。それらは同じことを表していますが、別の場合に使用されるだけです。

クラスに可変状態を導入して、適用する同等性ルールを示します。

決してそれをしないでください、これは壊れています:

  • 状態をグローバルに変更する場合、それは大きな魔法であり、特にこれらのクラスをより多くのスレッドで使用する場合、多くの問題を引き起こす可能性があります。いくつかの既存のマップなどを壊すことができます。
  • ローカルで変更すると、魔法は少なくなりますが、ほぼ確実に、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

これは、@cheekenによって最初に提案されたソリューションの改善です。些細なプロジェクトに取り組んでいない限り、2番目を採用しないことを強くお勧めします。2番目のアプローチでは、マップに配置したすべてのアイテムのハッシュが同じハッシャーで計算されるように強制することはできません。これにより、実行時に説明するのが難しい、誤った予期しない動作が発生する可能性があります。

正しい方法は、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)
}

パー(特別なハッシャーを使用する並列ハッシュマップを実装する必要があります)やマージ、空のメソッドなど、すべてのパブリックメソッドが正しく動作することを確認する必要があります HashMap.empty[A,B]CustomHashMap.empty[A,B]

于 2013-01-20T09:34:19.517 に答える
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 に答える