问题
Given a Map[Int, Set[Int]], how can I modify a single value of the Map, generating a new one in the process, for example:
val x = Map(1 -> Set(1,2,3))
x(1) + 5 // This creates a new Set, but not a new Map
val y = x(1) change { x => x + 5 }
// The previous functionality is what I'm looking for
// z: Set[Int]] = List(Set(1, 2, 3, 5))
回答1:
In scala 2.10:
implicit class ChangeableMap[K,V]( val m: Map[K,V] ) extends AnyVal {
def change( k: K )( transform: V => V ): Map[K,V] = {
m.get( k ).map{ v => m + (k-> transform(v)) }.getOrElse( m )
}
}
Some test:
scala>val x = Map(1 -> Set(1,2,3), 2 -> Set(4,5))
x: scala.collection.immutable.Map[Int,scala.collection.immutable.Set[Int]] = Map(1 -> Set(1, 2, 3), 2 -> Set(4, 5))
scala> x.change(1) { x => x + 5 }
res1: Map[Int,scala.collection.immutable.Set[Int]] = Map(1 -> Set(1, 2, 3, 5), 2 -> Set(4, 5))
If you're in scala 2.9, this will do:
class ChangeableMap[K,V]( m: Map[K,V] ) {
def change( k: K )( transform: V => V ): Map[K,V] = {
m.get( k ).map{ v => m + (k-> transform(v)) }.getOrElse( m )
}
}
implicit def toChangeableMap[K,V]( m: Map[K,V] ) = new ChangeableMap[K,V]( m )
回答2:
As Robin Green suggests, lenses are made for this job. In fact, you want a partial lens, since a map is a partial function of key -> value.
Scalaz 7 includes the mapVPLens function to make a partial lens (PLens) to the value at a chosen key:
import scalaz.PLens._
val x = Map(1 -> Set(1,2,3))
mapVPLens(1) mod ((_: Set[Int]) + 5, x) // Map(1 -> Set(1, 2, 3, 5))
Modifying the value at a non-existent key will have no effect:
mapVPLens(9) mod ((_: Set[Int]) + 5, x) // Map(1 -> Set(1,2,3))
回答3:
Use lenses!
However, Scalaz 6, which defines lenses, doesn't have a specific pre-made lens for your situation, which means slightly more work for you - though if your Map is in turn contained in another object, it does have (well-hidden) support for that situation. And Scalaz 7 will have a lens for standalone Maps.
Also, lenses are just pairs of functions, requiring no language support, so you could just roll your own.
回答4:
Here's one from our codebase.
/**
* Alters a value in a map.
*
* modifyMap :: Map k v -> k -> (Maybe v -> Maybe v) -> Map k v
* See Haskell's Data.Map.alter
*
* @param m the map to modify
* @param key the key to modify the value of
* @param mod a function that takes the existing value (if any) and returns an optional new value
*
* @return the modified map
*/
def modifyMap[K,V](m: Map[K,V], key: K)
(mod: (Option[V] ⇒ Option[V])): Map[K,V] = {
mod(m.get(key)) match {
case Some(newVal) ⇒ m + (key → newVal)
case None ⇒ m - key
}
}
And here's how you use it:
modifyMap(myMap, "someKey") {
case Some(someVal) =>
// present
if (condition)
Some(valueDerivedFrom(someVal)) // provide a new mapping for someKey
else
None // someKey will now be unset
case None =>
// wasn't present
if (condition)
Some(newValue) // provide a new value for someKey
else
None // leave someKey unset
}
回答5:
A very idiomatic way of solving this problem would be the following (thanks Viktor Klang):
val x = Map(1 -> Set(1,2,3), 2 -> Set(1), 3 -> Set(5))
x.map { case (1, v) => (1, v + 5); case x => x }
// res0: Map(1 -> Set(1, 2, 3, 5))
Or nicely packed into a class as well as an implicit:
class ChangeableMap[K,V](map:Map[K,V]) {
def change(index:K)(f:V => V) = map.map {
case (`index`, v:V) => (index, f(v))
case x => x
}
}
object ChangeableMap {
implicit def fromMap[K,V](map:Map[K,V]) = new ChangeableMap(map)
}
With the previous declaration, the following will work:
x.change(1) { x => x + 5 }
x.change(1) { _ + 5 }
// res1: Map(1 -> Set(1, 2, 3, 5))
Note that this is probably not the fastest solution, given that Scala will (probably, haven't confirmed) iterate over the entire map!
A possibly faster implementation would be the following (though I have not verified if it is actually faster):
class ChangeableMap[K,V](map:Map[K,V]) {
def change(index:K)(f:V => V) = map.get(index) match {
case Some(x) => map + ((index, f(x)))
case None => map
}
}
回答6:
I think the easiest way would be using scala.collection.mutable.Map.
import scala.collection.mutable.Map
val m = Map(1 -> Set(1,2,3))
m.update(1, m(1) + 5)
// now the Map looks like this: Map(1 -> Set(1,2,3,5))
If you get an immutable Map, you can simply convert it to a mutable one by using:
val n: collection.mutale.Map(m.toSeq: _*)
This also works the other way around, if you need to return an immutable Map.
回答7:
As mentioned before you can use Partial Lens for this sort of problem, scalaz and Monocle implements it. Here is how you would do it with Monocle:
import monocle.syntax.taversal._ // to use |->>
import monocle.syntax.at._ // to use at
val x = Map(1 -> Set(1,2,3))
x |->> at(1) modify(_ + 5) == Map(1 -> Set(1,2,3,5))
来源:https://stackoverflow.com/questions/14447242/how-to-modify-a-value-of-a-map-which-contains-sets-returning-a-new-map