Карта со ссылками на объекты в качестве ключей?

#scala #data-structures #map

#scala #структуры данных #словарь

Вопрос:

У меня есть объект, в котором хранится информация о конкретных экземплярах. Для этого я хотел бы использовать Map , но поскольку ключи не являются ссылочными (это не так, верно?) но в виде хэшей, предоставляемых getHashCode методом. Для лучшего понимания:

 import collection.mutable._
import java.util.Random

object Foo {
    var myMap = HashMap[AnyRef, Int]()

    def doSomething(ar: AnyRef): Int = {
        myMap.get(ar) match {
            case Some(x) => x
            case None => {
                myMap  = ar -> new Random().nextInt()
                doSomething(ar)
            }
        }
    }
}

object Main {
    def main(args: Array[String]) {
        case class ExampleClass(x: String);
        val o1 = ExampleClass("test1")
        val o2 = ExampleClass("test1")

        println(o2 == o1) // true
        println(o2 eq o1) // false

                    // I want the following two lines to yield different numbers
                    // and i do not have control over the classes, messing with their
                    // equals implementation is not possible.
        println(Foo.doSomething(o1))
        println(Foo.doSomething(o2))
    }
}
  

В случаях, когда у меня есть экземпляры с одинаковым хэш-кодом, «кэширование» для случайного значения вернет одно и то же значение для обоих экземпляров, даже если они не совпадают. Какой datastructed лучше всего использовать в этой ситуации?

Уточнение / Редактировать

Я знаю, как это работает обычно, на основе метода hashCode and equals . Но это именно то, чего я хочу избежать. Я обновил свой пример, чтобы сделать это более понятным. 🙂

Ответ №1:

РЕДАКТИРОВАТЬ: Основываясь на разъяснениях к вопросу, вы можете создать свою собственную реализацию Map и переопределить elemEquals().

Оригинальная реализация (в HashMap)

 protected def elemEquals(key1: A, key2: A): Boolean = (key1 == key2)
  

Измените это на:

 protected def elemEquals(key1: A, key2: A): Boolean = (key1 eq key2)

class MyHashMap[A <: AnyRef, B] extends scala.collection.mutable.HashMap[A, B] {
  protected override def elemEquals(key1: A, key2: A): Boolean = (key1 eq key2)
}
  

Обратите внимание, что для использования eq вам нужно ограничить использование ключа AnyRef или выполнить сопоставление в методе elemEquals().

 case class Foo(i: Int)
val f1 = new Foo(1)
val f2 = new Foo(1)
val map = new MyHashMap[Foo, String]()
map  = (f1 -> "f1")
map  = (f2 -> "f2")
map.get(f1) // Some(f1)
map.get(f2) // Some(f2)
  


Оригинальный ответ

Сопоставление работает с hashCode() и equals(). Правильно ли вы внедрили equals () в свои obejcts? Обратите внимание, что в Scala == преобразуется в вызов equals() . Чтобы получить такое же поведение == в Java, используйте оператор Scala eq

 case class Foo(i: Int)
val f1 = new Foo(1)
val f2 = new Foo(1)
f1 == f2 // true
f1.equals(f2) // true
f1 eq f2 // false

val map = new MyHashMap (f1 -> "f1", f2 -> "f2")
map.get(f1) // Some("f2")
map.get(f2) // Some("f2")
  

Здесь класс case реализует equals() для эквивалентности объектов, в данном случае:

 f1.i == f1.i
  

Вам нужно переопределить equals () в ваших объектах, чтобы включить равенство объектов, то есть что-то вроде:

 override def equals(o: Any) = { o.asInstanceOf[AnyRef] eq this }
  

Это все равно должно работать с тем же hashCode().

Комментарии:

1. Смотрите мой комментарий к ответу huynhjl . 🙂

2. @Malax не понимаю, я говорю, что вам нужно переопределить equals в ваших классах и использовать eq, а не equals() /==

Ответ №2:

Вы также можете использовать IdentityHashMap вместе с scala.collection.JavaConversions .

Ответ №3:

Ах, основанный на комментарии… Вы могли бы использовать оболочку, которая переопределяет equal, чтобы иметь ссылочную семантику.

 class EqWrap[T <: AnyRef](val value: T) {
  override def hashCode() = if (value == null) 0 else value.hashCode
  override def equals(a: Any) = a match {
    case ref: EqWrap[_] => ref.value eq value
    case _ => false
  }
}
object EqWrap {
  def apply[T <: AnyRef](t: T) = new EqWrap(t)
}

case class A(i: Int)

val x = A(0)
val y = A(0)

val map = Map[EqWrap[A], Int](EqWrap(x) -> 1)
val xx = map.get(EqWrap(x))
val yy = map.get(EqWrap(y))
//xx: Option[Int] = Some(1)
//yy: Option[Int] = None
  

Оригинальный ответ (основанный на непонимании вопроса — я должен оставить это, чтобы комментарий имел смысл …)

Карта уже имеет эту семантику (если я не понимаю вашего вопроса).

 scala> val x = A(0)
x: A = A(0)

scala> val y = A(0)
y: A = A(0)

scala> x == y
res0: Boolean = true // objects are equal

scala> x.hashCode
res1: Int = -2081655426

scala> y.hashCode
res2: Int = -2081655426 // same hash code

scala> x eq y
res3: Boolean = false // not the same object

scala> val map = Map(x -> 1)
map: scala.collection.immutable.Map[A,Int] = Map(A(0) -> 1)

scala> map(y)
res8: Int = 1 // return the mapping based on hash code and equal semantic
  

Комментарии:

1. Да, это то, чего я хочу избежать. Я хочу, чтобы все было в принципе наоборот. В вашем примере map(y) не должно ничего возвращать, потому что y само по себе не находится внутри Map .