Reputation: 417
I want to merge two maps in Scala, giving a specific function for combining values.
I really like map1 |+| map2 in scalaz, however I can't find a way how to substitute the + with something else. For example, I would like to pick the max value, or the min value for each key. Ideally, the operator would be provided as an external function, so something of the sort:
def op (a, b) = min(a,b)
map1 |op| map2
Which would give all the keys with the corresponding min values from both maps.
Upvotes: 0
Views: 323
Reputation: 8932
The Map
semigroup uses the semigroup for its value type to merge the values for each key. If you want different behaviour, you can just use a type wrapper for the values whose semigroup has different behaviour.
import scalaz._, Scalaz._
val map1 = Map("foo" → 1, "bar" → 2)
val map2 = Map("foo" → 3)
val default = map1 |+| map2
// Map(foo → 4, bar → 2): Map[String, Int]
val min = map1.mapValues(Tags.MinVal(_)) |+| map2.mapValues(Tags.MinVal(_))
// Map(foo → 1, bar → 2): Map[String, Int @@ Tags.MinVal]
You can “unwrap” the tagged values with .unwrap
import scalaz.syntax.tag._
min.mapValues(_.unwrap)
// Map(foo → 1, bar → 2): Map[String, Int]
Creating your own type wrapper is straightforward. You simply wrap your type in another type which has an appropriately defined type class instance. For example, if you wanted to create your own version of a Tags.MaxVal
you could write:
case class Max[A: Order](unwrap: A)
object Max {
implicit def semigroup[A: Order]: Semigroup[Max[A]] =
new Semigroup[Max[A]] {
def append(f1: Max[A], f2: ⇒ Max[A]) =
Max(Order[A].max(f1.unwrap, f2.unwrap))
}
}
val max = map1.mapValues(Max(_)) |+| map2.mapValues(Max(_))
max.mapValues(_.unwrap)
// Map(foo → 3, bar → 2): Map[String, Int]
Alternatively, use can use the Scalaz Tag
stuff to define wrappers in a similar fashion; see scalaz.Tag
, scalaz.Tags
, and scalaz.std.AnyValInstances
for examples of this.
Upvotes: 3