Scala: Почему mapValues ​​создает представление и есть ли стабильные альтернативы?

Просто сейчас я удивлен, узнав, что mapValues производит представление. Следствие показано в следующем примере:

case class thing(id: Int)
val rand = new java.util.Random
val distribution = Map(thing(0) -> 0.5, thing(1) -> 0.5)
val perturbed = distribution mapValues { _ + 0.1 * rand.nextGaussian }
val sumProbs = perturbed.map{_._2}.sum
val newDistribution = perturbed mapValues { _ / sumProbs }

Идея состоит в том, что у меня есть распределение, которое возмущено некоторой случайностью, тогда я перенормирую его. Код фактически терпит неудачу в своем первоначальном намерении: так как mapValues производит view, _ + 0.1 * rand.nextGaussian всегда пересматривается всякий раз, когда perturbed используется.

Я сейчас делаю что-то вроде distribution map { case (s, p) => (s, p + 0.1 * rand.nextGaussian) }, но это только немного многословно. Итак, цель этого вопроса:

  1. Напомните людям, которые не знают об этом факте.
  2. Ищите причины, почему они делают mapValues выход views.
  3. Есть ли альтернативный метод, который производит бетон Map,
  4. Существуют ли другие распространенные методы сбора, которые имеют эту ловушку.

Благодарю.

2 ответа

Решение

Об этом есть билет, SI-4776 (от YT).

Коммит, который вводит его, имеет следующее:

Следуя предложению Джрудольфа, сделал filterKeys а также mapValues преобразовывать абстрактные карты и дублировать функциональность для неизменяемых карт. Перевезу transform а также filterNot от неизменных к общим картам. Обзор Фаллер.

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

В качестве обходного пути можно сделать mapValues(...).view.force производить новый Map,

В Scala Doc говорят:

вид карты, который отображает каждый key этой карты в f(this(key)), Полученная карта оборачивает исходную карту без копирования каких-либо элементов.

Так что этого следовало ожидать, но меня это сильно пугает, завтра мне придется пересмотреть кучу кода. Я не ожидал такого поведения:-(

Просто другой обходной путь:

Ты можешь позвонить toSeq чтобы получить копию, и если вам нужно вернуть ее на карту toMap, но это ненужное создание объектов и влияет на производительность по сравнению с использованием map

Можно относительно легко написать, mapValues который не создает представление, я сделаю это завтра и выложу код здесь, если никто не сделает это до меня;)

РЕДАКТИРОВАТЬ:

Я нашел простой способ "форсировать" представление, использовать ".map(identity)" после mapValues ​​(поэтому не нужно реализовывать определенную функцию):

scala> val xs = Map("a" -> 1, "b" -> 2)
xs: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2)

scala> val ys = xs.mapValues(_ + Random.nextInt).map(identity)
ys: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1315230132, b -> 1614948101)

scala> ys
res7: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1315230132, b -> 1614948101)

Обидно, что возвращаемый тип на самом деле не является представлением! иначе можно было бы назвать "силой"...

Лучше (и не рекомендуется) в scala 2.13, теперь возвращает MapView:API Doc

Другие вопросы по тегам