Вложенные карты по умолчанию в Scala

Я пытаюсь построить вложенные карты в Scala, где и внешняя, и внутренняя карта используют метод "withDefaultValue". Например, следующее:

val m = HashMap.empty[Int, collection.mutable.Map[Int,Int]].withDefaultValue( HashMap.empty[Int,Int].withDefaultValue(3))
m(1)(2)
res: Int = 3
m(1)(2) = 5
m(1)(2) 
res: Int = 5
m(2)(3) = 6
m
res : scala.collection.mutable.Map[Int,scala.collection.mutable.Map[Int,Int]] = Map()

Таким образом, карта, обращаясь к соответствующим клавишам, возвращает мне то, что я вставляю. Однако сама карта кажется пустой! Даже m.size возвращает 0 в этом примере. Кто-нибудь может объяснить, что здесь происходит?

Ответ 1

Короткий ответ

Это определенно не ошибка.

Длинный ответ

Поведение withDefaultValue заключается в том, чтобы сохранить значение по умолчанию (в вашем случае, измененную карту) внутри Map, которое будет возвращено в случае, если ключ не существует. Это не то же самое, что значение, которое вставляется в карту, когда ключ не найден.

Посмотрите внимательно на то, что происходит. Это будет легче понять, если мы потянем карту по умолчанию как отдельную переменную, чтобы мы могли проверить ее по своему усмотрению; назовем его default

import collection.mutable.HashMap
val default = HashMap.empty[Int,Int].withDefaultValue(3)

Итак, default - изменяемое отображение (которое имеет собственное значение по умолчанию). Теперь мы можем создать m и дать default как значение по умолчанию.

import collection.mutable.{Map => MMap}
val m = HashMap.empty[Int, MMap[Int,Int]].withDefaultValue(default)

Теперь, когда m получает доступ с отсутствующим ключом, он возвращает default. Обратите внимание, что это то же самое поведение, что и у вас, потому что withDefaultValue определяется как:

def withDefaultValue (d: B): Map[A, B]

Обратите внимание, что это d: B, а не d: => B, поэтому он не будет создавать новую карту каждый раз, когда будет доступен доступ по умолчанию; он вернет тот же самый точный объект, который мы назвали default.

Итак, посмотрим, что получится:

m(1) // Map()

Поскольку ключ 1 не находится в m, возвращается значение по умолчанию, default. default в это время представляет собой пустую карту.

m(1)(2) = 5

Так как m(1) возвращает default, эта операция сохраняет 5 как значение для ключа 2 в default. На карту m ничего не записывается, потому что m(1) разрешает default, которая является отдельной Картой полностью. Мы можем проверить это, просмотрев default:

default // Map(2 -> 5)

Но, как мы сказали, m остается неизменным

m // Map()

Теперь, как добиться того, чего вы действительно хотели? Вместо использования withDefaultValue вы хотите использовать getOrElseUpdate:

def getOrElseUpdate (key: A, op: ⇒ B): B

Обратите внимание, как мы видим op: => B? Это означает, что аргумент op будет переучитываться каждый раз, когда это необходимо. Это позволяет нам помещать новую карту в нее и иметь отдельную новую карту для каждого недействительного ключа. Давайте посмотрим:

val m2 = HashMap.empty[Int, MMap[Int,Int]]

Здесь не требуются значения по умолчанию.

m2.getOrElseUpdate(1, HashMap.empty[Int,Int].withDefaultValue(3)) // Map()

Ключ 1 не существует, поэтому мы вставляем новый HashMap и возвращаем это новое значение. Мы можем проверить, что он был вставлен так, как мы ожидали. Обратите внимание: 1 сопоставляется с недавно добавленной пустой картой и что они не добавлены нигде из-за описанного выше поведения.

m2 // Map(1 -> Map())

Аналогично, мы можем обновить карту, как ожидалось:

m2.getOrElseUpdate(1, HashMap.empty[Int,Int].withDefaultValue(1))(2) = 6

и убедитесь, что он был добавлен:

m2 // Map(1 -> Map(2 -> 6))

Ответ 2

withDefaultValue используется для возврата значения, когда ключ не был найден. Он не заполняет карту. Таким образом, вы остаетесь пустыми. Скорее всего, используя getOrElse(a, b), где b предоставляется withDefaultValue.

Ответ 3

То, что вы видите, - это эффект, который вы создали для одного Map[Int, Int], это значение по умолчанию, когда ключ не находится на внешней карте.

scala> val m = HashMap.empty[Int, collection.mutable.Map[Int,Int]].withDefaultValue( HashMap.empty[Int,Int].withDefaultValue(3))
m: scala.collection.mutable.Map[Int,scala.collection.mutable.Map[Int,Int]] = Map()

scala> m(2)(2)
res1: Int = 3

scala> m(1)(2) = 5

scala> m(2)(2)
res2: Int = 5

Чтобы получить тот эффект, который вы ищете, вам придется обернуть Map реализацией, которая фактически вставляет значение по умолчанию, когда ключ не найден в Map.

Edit:

Я не уверен, каков ваш фактический вариант использования, но вам может быть проще использовать пару для ключа для одного Map.

scala> val m = HashMap.empty[(Int, Int), Int].withDefaultValue(3)
m: scala.collection.mutable.Map[(Int, Int),Int] = Map()

scala> m((1, 2))
res0: Int = 3

scala> m((1, 2)) = 5

scala> m((1, 2))
res3: Int = 5

scala> m
res4: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,2) -> 5)

Ответ 4

У меня была точно такая же проблема, и я был счастлив найти ответ dhg. Поскольку набирать getOrElseUpdate все время не очень кратким, я придумал это небольшое расширение идеи, которую хочу поделиться: Вы можете объявить класс, который использует getOrElseUpdate как поведение по умолчанию для оператора():

class DefaultDict[K, V](defaultFunction: (K) => V) extends HashMap[K, V] {
  override def default(key: K): V = return defaultFunction(key)
  override def apply(key: K): V = 
    getOrElseUpdate(key, default(key))
}

Теперь вы можете делать то, что хотите сделать следующим образом:

var map = new DefaultDict[Int, DefaultDict[Int, Int]](
  key => new DefaultDict(key => 3))
map(1)(2) = 5

Что теперь приводит к map, содержащему 5 (или скорее: содержащий DefaultDict, содержащий значение 5 для ключа 2).

Ответ 5

Я знаю это немного позже, но я только что видел сообщение, когда я пытался решить ту же проблему.
Вероятно, API отличается от версии 2012 года, но вы можете использовать withDefault вместо этого withDefaultValue.
Разница в том, что withDefault принимает функцию как параметр, которая выполняется каждый раз, когда запрашивается пропущенный ключ;)