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) }
, но это только немного многословно. Итак, цель этого вопроса:
- Напомните людям, которые не знают об этом факте.
- Ищите причины, почему они делают
mapValues
выходview
s. - Есть ли альтернативный метод, который производит бетон
Map
, - Существуют ли другие распространенные методы сбора, которые имеют эту ловушку.
Благодарю.
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