Scala: ¿Por qué mapValues ​​produce una vista y hay alguna alternativa estable?

Ahora mismo me sorprende mapValues que mapValues produce una vista. La consecuencia se muestra en el siguiente ejemplo:

 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 } 

La idea es que tengo una distribución, que se perturba con cierta aleatoriedad, luego la renormalizo. El código realmente falla en su intención original: dado que mapValues produce una view , _ + 0.1 * rand.nextGaussian siempre se vuelve a evaluar cuando se usa perturbed .

Ahora estoy haciendo algo así como el distribution map { case (s, p) => (s, p + 0.1 * rand.nextGaussian) } , pero eso es solo un poco detallado. Entonces el propósito de esta pregunta es:

  1. Recuérdele a las personas que no conocen este hecho.
  2. Busque los motivos por los que hacen mapValues output view s.
  3. Si hay un método alternativo que produce un Map concreto.
  4. ¿Hay algún otro método de recolección comúnmente utilizado que tenga esta trampa?

Gracias.

Hay un boleto sobre esto, SI-4776 (por YT).

El compromiso que lo presenta tiene esto que decir:

Siguiendo una sugerencia de jrudolph, filterKeys y mapValues transforman mapas abstractos y duplican funcionalidad para mapas inmutables. transform y filterNot movidos filterNot desde inmutables a mapas generales. Reseña por phaller.

No he podido encontrar la sugerencia original de jrudolph, pero supongo que se hizo para hacer mapValues más eficiente. Haga la pregunta, eso puede ser una sorpresa, pero mapValues es más eficiente si no es probable que itere sobre los valores más de una vez.

Como mapValues(...).view.force , uno puede hacer mapValues(...).view.force para producir un nuevo Map .

El scala doc dice:

una vista de mapa que asigna todas las key de este mapa a f(this(key)) . El mapa resultante envuelve el mapa original sin copiar ningún elemento.

Así que esto debería esperarse, pero esto me asusta mucho, tendré que revisar un montón de código mañana. No esperaba un comportamiento como ese 🙁

Solo otra solución:

Puede llamar a toSeq para obtener una copia, y si la necesita volver a mapear a toMap , pero esto no es necesario crear objetos, y tener una implicancia de rendimiento sobre el uso del map

Uno puede escribir de manera relativamente fácil, un mapValues que no crea una vista, lo haré mañana y publique el código aquí si nadie lo hace antes que yo;)

EDITAR:

Encontré una manera fácil de ‘forzar’ la vista, usar ‘.map (identidad)’ después de mapValues ​​(así que no es necesario implementar una función específica):

 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) 

Es una pena que el tipo devuelto no sea realmente una vista. de lo contrario, uno podría haber llamado ‘fuerza’ …