Scala: Remove none elements from map and flatten - scala

I have a map:
Map("key1" -> Some("value1"), "key2" -> None, "key3" -> Some("value3"))
I want to remove all None elements and flatten the map. What is the easiest way to accomplish that? I only found this way:
Map("key1" -> Some("value1"), "key2" -> None, "key3" -> Some("value3")).filter(_._2.nonEmpty).map(item => (item._1 -> item._2.getOrElse(Nil)))
The result is:
Map(key1 -> value1, key3 -> value3)
Do you know a better way?

My take using pattern matching is:
Map("key1" -> Some("value1"), "key2" -> None, "key3" -> Some("value3")).collect {
case (key, Some(value)) => key -> value
}
// Map(key1 -> value1, key3 -> value3)
Collect acts like combined map + filter

You can use for-comprehension + pattern-matching:
for((k, Some(v)) <- yourMap) yield k -> v

Using partition over the map, like this,
val (flattened,_) = map.partition(_._2.isDefined)

My take using for comprehensions:
val m = Map("key1" -> Some("value1"), "key2" -> None, "key3" -> Some("value3"))
for( (key,value) <- m if(value.isDefined)) yield (key,value.get)

You could define the following helpers too, which allow for more compact syntax
implicit class RichPairedOptionIterableOps[A, B, Repr[_]](
iterable: IterableOps[(A, Option[B]), Repr, Repr[(A, Option[B])]]
) {
def collectWithSome: Repr[(A, B)] = iterable.collect { case (a, Some(b)) => a -> b }
def collectWithNone: Repr[A] = iterable.collect { case (a, None) => a }
}
on your example:
Map("key1" -> Some("value1"), "key2" -> None, "key3" -> Some("value3")).collectWithSome

Related

How to reverse Map

Trying to reverse Map and the output is only 2 element
val occurrences: Map[String, Int] = arr.groupMapReduce(identity)(_ => 1)(_ + _)
Output: HashMap(world -> 2, Hello, -> 1, hello, -> 1, hello -> 2, and -> 1, world, -> 1)
val reversed = for ((k,v) <- occurrences) yield (v, k)
Output: HashMap(1 -> world,, 2 -> hello)
How did I lost the other patameters?
Similar to #user proposal, but trying to be a little bit more efficient.
def invertMap[K, V](map: Map[K, V]): Map[V, List[K]] =
map
.view
.groupMap(_._2)(_._1)
.view
.mapValues(_.toList)
.toMap
The performance difference would probably be negligible so go with the one you find more readable.
As #Luis Miguel Mejía Suárez said, you can't duplicate keys in a Map, so when you try to make the values the keys, some of the entries are lost.
You can instead do this to obtain a Map[Int, List[String]]
val occurrences = Map("world" -> 2, "Hello," -> 1, "hello," -> 1, "hello" -> 2, "and" -> 1, "world," -> 1)
val x: Map[Int, List[String]] =
occurrences.toList
.groupBy { case (k, v) => v }
.view.mapValues(v => v.map(_._1))
.toMap
Output:
Map(1 -> List(Hello,, hello,, and, world,), 2 -> List(world, hello))
P.S. The .view and .toMap stuff is because mapValues on MapOps is deprecated for now. There'll be a proper strict version later, though.

How to transform two Maps into a single Map

Suppose I have two Maps like these
Map(("a" -> "x-100"), ("b" -> "x-200"), ("c" -> "x-300"))
Map(("a" -> "y-100"), ("b" -> "y-200"), ("c" -> "y-300"))
What would be the simplest way to transform them into the following (assuming that all the values are unique and with possibly different lengths)?
Map(("x-100" -> "y-100"), ("x-200" -> "y-200"), ("x-300" -> "y-300"))
Or with a for comprehension and some safety:
for{
(k, v1) <- m1
v2 = m2.get(k)
if (v2.isDefined)
} yield (v1 -> v2.get)
This returns Map(x-100 -> y-100, x-200 -> y-200, x-300 -> y-300)
Check ScalaFiddle
val m1 = Map(("a" -> "x-100"), ("b" -> "x-200"), ("c" -> "x-300"),("d" -> "ignored"))
val m2 = Map(("a" -> "y-100"), ("b" -> "y-200"), ("c" -> "y-300"))
m1.keySet.intersect(m2.keySet).map(k=>m1(k)->m2(k)).toMap
You can do it like this:
val a = Map(("a" -> "x-100"), ("b" -> "x-200"), ("c" -> "x-300"))
val b = Map(("a" -> "y-100"), ("b" -> "y-200"), ("c" -> "y-300"))
def zipMaps(map1: Map[String, String], map2: Map[String, String]) = {
for(key <- map1.keys ++ map2.keys)
yield (key, map1.get(key), map2.get(key))
}
val result = zipMaps(a,b).map{
case (k,Some(v1),Some(v2)) => (v1,v2)
case (k,_ ,_)=> ("", "")
}.toMap.filterKeys(_ != "")
// result = Map(x-100 -> y-100, x-200 -> y-200, x-300 -> y-300)
val a = Map("a" -> "x-100", "b" -> "x-200", "c" -> "x-300")
val b = Map("a" -> "y-100", "b" -> "y-200", "c" -> "y-300")
val c = a.map {
case (k, v) => v -> b(k)
}
println(c) // Map(x-100 -> y-100, x-200 -> y-200, x-300 -> y-300)

Inverting a Map[Long, Set[Long]) to a Map[Long, Long]

Trying to convert a Map[Long, Set[Long]] to a Map[Long, Long].
I tried this but having compile issues:
m.map(_.swap).map(k => k._1.map((_, k._2)))
Example:
Map(10 -> Set(1,2,3), 11 -> Set(4,5))
Should become:
Map(1 -> 10,
2 -> 10,
3 -> 10,
4 -> 11,
5 -> 11)
flatMap on Map[A,B] will "just work" with collections of tuples:
m.flatMap {case (k,v) => v.map(_ -> k)} // Map[Long,Long]
going from a Map[Long,Set[Long]] to a series of Set[(Long,Long)] that gets flattened to a Map[Long,Long].
Assuming in is your Map[Long, Set[Long]]:
in./:(Map.empty[Long, Long]) { case (acc, (key, values)) => acc ++ values.map(_ -> key) }
To clarify, seem like you have this:
Map(10 -> Set(1,2,3), 11 -> Set(4,5))
And you want to convert this map in another map, but with something like this:
Map(1 -> 10,
2 -> 10,
3 -> 10,
4 -> 11,
5 -> 11)
As you can see if the sets are not disjoint, some keys in the resulted map with be missing:
Having this in consideration, the code will look like this:
val m: Map[Long, Set[Long]] = Map(10l -> Set(1l,2l,3l), 11l -> Set(4l,5l))
m.map(_.swap).map(k => k._1.map((_, k._2)))
val foo: Iterable[(Long, Long)] = m.flatMap { t =>
val (key, value) = t
value.map(_ -> key)
}
val result: Map[Long, Long] = foo.toMap
This will invert your Map m from Map[Long, Set[Long]] to Map[Long, List[Long]].
m flatten {case(k, vs) => vs.map((_, k))} groupBy (_._1) mapValues {_.map(_._2)}
You haven't specified what should happen when different Set values contains some of the same Longs (i.e. Map(8 -> Set(1,2), 9 -> Set(2,3))). If you're sure that won't happen you can use the following adjustment.
m flatten {case(k, vs) => vs.map((_, k))} groupBy (_._1) mapValues {_.head._2}
Or even more simply:
m.flatten {case(k, vs) => vs.map((_, k))}.toMap

Convert List[Map[String,Map[String,Int]]] to Map[Int,Int] in Scala

Given the following:
val t: List[Map[String, Map[String, Int]]] = List(
Map("a" -> Map("m" -> 1, "l" -> 21)),
Map("a" -> Map("m" -> 2, "l" -> 22)),
Map("a" -> Map("m" -> 3, "l" -> 23)),
Map("a" -> Map("m" -> 4, "l" -> 24))
)
I want the result:
Map(1->21,2->22,3->23,4->24)
What I have so far is:
val tt = (for {
(k,v) <- t
newKey = v("m")
newVal = v("l")
} yield Map(newKey -> newVal)).flatten.toMap
But this does not type check so Im missing some basic understanding since I cant understand why not?
My questions are:
Why is my code faulty?
What would be the most idiomatic way to do the transformation I want?
You've got List[Map[...]], not Map[...] so you want to unpack that first.
val tt = (for {
map <- t
(k, v) <- map
} ...)
t
.iterator
.flatMap(_.values)
.map { v => v("m") -> v("l") }
.toMap

Scala Map Transformation

Can someone recommend a functional way to transform the map specified below from
Map("host.config.autoStart.powerInfo[1].startOrder" -> -1,
"host.config.autoStart.powerInfo[1].startAction" -> "None",
"host.config.autoStart.powerInfo[1].key" -> "vm-XXX",
"host.config.autoStart.powerInfo[0].key" -> "vm-YYY",
"host.config.autoStart.powerInfo[0].startOrder" -> -1,
"host.config.autoStart.powerInfo[0].startAction" -> "None")
to
Map("host.config.autoStart.powerInfo" -> Map(
1 -> Map("startOrder" -> -1,
"startAction" -> "None",
"key" -> "vm-639"),
0 -> Map("startOrder" -> -1,
"startAction" -> "None",
"key" -> "vm-641")))
Extract what is before the subscript and make that the key
Extract the number between the subscript [x] and make that the key of value
A one line (long) solution:
val R = """([^\[]+)\[(\d+)\]\.(.+)""".r
m.map{ case(R(h,i,k),v) => (h,i,k,v) }.groupBy(_._1).mapValues(_.groupBy(_._2).mapValues{ _.map{case(h,i,k,v) => (k,v)}.toMap} )
res1: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,Any]]] =
Map(host.config.autoStart.powerInfo ->
Map(1 -> Map(startAction -> None,
startOrder -> -1,
key -> vm-XXX),
0 -> Map(key -> vm-YYY,
startAction -> None,
startOrder -> -1)
))
Or write it more or less readable:
m.map{ case(R(h,i,k),v) => (h,i,k,v) }
.groupBy(_._1).mapValues{ value =>
value.groupBy(_._2).mapValues{ _.map{case(h,i,k,v) => (k,v)}.toMap}
}
Edit: added some comments to the code to make it easier to see what's going on
Copied from my REPL:
scala> val re = """(.+)\[(\d+)\]\.(.+)""".r // Creates a regex to grab the key values
re: scala.util.matching.Regex = (.+)\[(\d+)\]\.(.+)
scala> val m = Map("host.config.autoStart.powerInfo[1].startOrder" -> -1,"host.config.autoStart.powerInfo[1].startAction" -> "None","host.config.autoStart.powerInfo[1].key" -> "vm-XXX","host.config.autoStart.powerInfo[0].key" -> "vm-YYY","host.config.autoStart.powerInfo[0].startOrder" -> -1,"host.config.autoStart.powerInfo[0].startAction" -> "None")
m: scala.collection.immutable.Map[String,Any] = Map(host.config.autoStart.powerInfo[0].key -> vm-YYY, host.config.autoStart.powerInfo[0].startAction -> None, host.config.autoStart.powerInfo[0].startOrder -> -1, host.config.autoStart.powerInfo[1].startAction -> None, host.config.autoStart.powerInfo[1].startOrder -> -1, host.config.autoStart.powerInfo[1].key -> vm-XXX)
scala> val tempList = m map { // Construct a temporary list of Tuples with all relevant values
| case (key, value) => key match {
| case re(p, i, k) => (p, i, k, value)
| }}
tempList: scala.collection.immutable.Iterable[(String, String, String, Any)] = List((host.config.autoStart.powerInfo,0,key,vm-YYY), (host.config.autoStart.powerInfo,0,startAction,None), (host.config.autoStart.powerInfo,0,startOrder,-1), (host.config.autoStart.powerInfo,1,startAction,None), (host.config.autoStart.powerInfo,1,startOrder,-1), (host.config.autoStart.powerInfo,1,key,vm-XXX))
scala> val accumulator = Map[String, Map[String, Map[String, Any]]]()
accumulator: scala.collection.immutable.Map[String,Map[String,Map[String,Any]]] = Map()
scala> val result = tempList.foldLeft(accumulator) {
| case (acc, e) => {
| val middleMap = acc.getOrElse(e._1, Map[String, Map[String, Any]]())
| val innerMap = middleMap.getOrElse(e._2, Map[String, Any]())
| acc + (e._1 -> (middleMap + (e._2 -> (innerMap + (e._3 -> e._4)))))
| }}
result: scala.collection.immutable.Map[String,Map[String,Map[String,Any]]] = Map(host.config.autoStart.powerInfo -> Map(0 -> Map(key -> vm-YYY, startAction -> None, startOrder -> -1), 1 -> Map(startAction -> None, startOrder -> -1, key -> vm-XXX)))