Как получить доступ/инициализировать и обновить значения в изменяемой карте?

Рассмотрим простую проблему использования изменяемой карты для отслеживания вхождений/подсчетов, то есть с помощью:

val counts = collection.mutable.Map[SomeKeyType, Int]()

Мой текущий подход к увеличению количества:

counts(key) = counts.getOrElse(key, 0) + 1
// or equivalently
counts.update(key, counts.getOrElse(key, 0) + 1)

Это как-то немного неуклюже, потому что я должен указать ключ дважды. Что касается производительности, я бы также ожидал, что key должен быть расположен дважды на карте, чего я бы хотел избежать. Интересно, что эта проблема доступа и обновления не возникла бы, если бы Int предоставил некоторый механизм для изменения самого себя. Переход от класса Int к Counter который обеспечивает функцию increment, например, разрешит:

// not possible with Int
counts.getOrElseUpdate(key, 0) += 1
// but with a modifiable counter
counts.getOrElseUpdate(key, new Counter).increment

Как-то я всегда ожидаю, что буду иметь следующую функциональность с изменяемой картой (несколько похожей на transform но без возврата новой коллекции и на конкретный ключ со значением по умолчанию):

// fictitious use
counts.updateOrElse(key, 0, _ + 1)
// or alternatively
counts.getOrElseUpdate(key, 0).modify(_ + 1)

Однако, насколько я вижу, такой функции не существует. Разве это не имеет смысла вообще (производительность и синтаксис мудрый) иметь такую возможность f: A => A на месте? Наверное, я просто что-то пропустил здесь... Наверное, должно быть какое-то лучшее решение этой проблемы, чтобы сделать такую функциональность ненужной?

Обновить:

Я должен был уточнить, что мне известно о withDefaultValue но проблема остается прежней: выполнение двух запросов еще в два раза медленнее одного, независимо от того, является ли это операцией O (1) или нет. Честно говоря, во многих ситуациях я был бы более чем счастлив добиться ускорения фактора 2. И, очевидно, конструкция закрытия модификации часто может перемещаться за пределы цикла, поэтому imho это не большая проблема по сравнению с запуском без необходимости дважды.

Ответ 1

Начиная с Scala 2.13, Option[V]):Option[V] rel="nofollow noreferrer"> Map#updateWith служит именно для этой цели:

map.updateWith("a")({
  case Some(count) => Some(count + 1)
  case None        => Some(1)
})

def updateWith (ключ: K) (remappingFunction: (Option [V]) => Option [V]): Option [V]


Например, если ключ не существует:

val map = collection.mutable.Map[String, Int]()
// map: collection.mutable.Map[String, Int] = HashMap()

map.updateWith("a")({ case Some(count) => Some(count + 1) case None => Some(1) })
// Option[Int] = Some(1)
map
// collection.mutable.Map[String, Int] = HashMap("a" -> 1)

и если ключ существует:

map.updateWith("a")({ case Some(count) => Some(count + 1) case None => Some(1) })
// Option[Int] = Some(2)
map
// collection.mutable.Map[String, Int] = HashMap("a" -> 2)

Ответ 2

Вы можете создать карту со значением по умолчанию, что позволит вам сделать следующее:

scala> val m = collection.mutable.Map[String, Int]().withDefaultValue(0)
m: scala.collection.mutable.Map[String,Int] = Map()

scala> m.update("a", m("a") + 1)

scala> m
res6: scala.collection.mutable.Map[String,Int] = Map(a -> 1)

Как упоминалось в описании Impredicative, поиск карт происходит быстро, поэтому я не буду беспокоиться о 2 поиске.

Обновить:

Как отметил Дебилски, вы можете сделать это еще проще, выполнив следующее:

scala> val m = collection.mutable.Map[String, Int]().withDefaultValue(0)
scala> m("a") += 1
scala> m
 res6: scala.collection.mutable.Map[String,Int] = Map(a -> 1)

Ответ 3

Я хотел ленить-инициализировать мою измененную карту вместо того, чтобы делать сгиб (для эффективности памяти). Метод collection.mutable.Map.getOrElseUpdate() подходит для моих целей. Моя карта содержала изменяемый объект для суммирования значений (опять же, для эффективности).

        val accum = accums.getOrElseUpdate(key, new Accum)
        accum.add(elem.getHours, elem.getCount)

collection.mutable.Map.withDefaultValue() не сохраняет значение по умолчанию для следующего запрошенного ключа.