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)))
Related
input:
type DeviceType = Any
val rh: Map[(String, String), String] = Map(("ss", "ex-ss") -> "MobileType", ("ud", "ex-ud") -> "DesktopType")
val lh: Map[String, DeviceType] = Map("ex-ss" -> 1, "ex-ud" -> "A#F4CC", "ex-zip" -> 30052)
Expected output:
val res:Map[String, DeviceType] = Map("ss" -> 1, "ud" -> "A#F4CC", "ex-zip" -> 30052)
So, basically you would need to create one more map to store connection between the tuples, from rh map.
Please, see code below for more details:
// Create device synonyms index, for instance: "ex-ss" -> "ss"
val deviceSynonyms: Map[String, String] = rh.keys.map(_.swap).toMap
val res: Map[String, DeviceType] = lh.map {
case (deviceName, deviceType) => deviceSynonyms.getOrElse(deviceName, deviceName) -> deviceType
}
Which will produce desired result:
Map(ss -> 1, ud -> A#F4CC, ex-zip -> 30052)
The solution is :
type DeviceType = Any
val rh: Map[(String, String), String] = Map(("ss", "ex-ss") -> "MobileType", ("ud", "ex-ud") -> "DesktopType")
val lh: Map[String, DeviceType] = Map("ex-ss" -> 1, "ex-ud" -> "A#F4CC", "ex-zip" -> 30052)
val rhKeySet = rh.keySet
val res: Map[String, DeviceType] = lh.map {
case (k,v) =>
if (rh.exists(_._1._2 == k)) {
val res = rhKeySet.filter(_._2 == k).head
res._1 -> v
}else{
k -> v
}
}
The output:
Map(ss -> 1, ud -> A#F4CC, ex-zip -> 30052)
Let me know if it helps!!
I have the following Map[String, Int]:
val m = Map[String, Int](
"[LOGIN-011]" -> 0,
"[LOGIN-103]" -> 3,
"[LOGIN-222]" -> 10,
"[ERROR-110]" -> 1,
"[ERROR-012]" -> 3,
...
)
How to find duplicated values in the Map and print the values with List[String] as follows:
3 -> List("[LOGIN-103]", "[ERROR-012]")
Try
m
.toSeq
.groupBy { case (key, value) => value }
.collect { case (key, values: List[(String, Int)]) if values.size > 1 => (key, values.map(_._1)) }
which outputs
HashMap(3 -> List([ERROR-012], [LOGIN-103]))
Here is Luis' one-liner:
m.groupBy(_._2).collect { case (key, group: Map[String, Int]) if group.size > 1 => (key, group.keySet) }
Following works in scala 2.13+ only
val map = Map (
"[LOGIN-011]" -> 0,
"[LOGIN-103]" -> 3,
"[LOGIN-222]" -> 10,
"[ERROR-110]" -> 1,
"[ERROR-012]" -> 3
)
val duplicateValues = map.groupMap(_._2)(_._1).filterNot(_._2.sizeIs == 1)
//Map(3 -> List([ERROR-012], [LOGIN-103]))
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)
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
I have nested maps with a key -> Map(key1 -> Map(), key2 -> Map()) kinda representation, which basically represent the path structure of a particular HTTP request made.
root/twiki/bin/edit/Main/Double_bounce_sender
root/twiki/bin/rdiff/TWiki/NewUserTemplate
I have stored them in a Map of maps which would give me the hierarchy of the path. Using a parser I read the data off server logs and get the corresponding data needed and then index the data in a sorted map.
val mainList: RDD[List[String]] = requesturl flatMap ( r => r.toString split("\\?") map (x => parser(x.split("/").filter(x => !x.contains("=")).toList).valuesIterator.toList))
def parser(list: List[String]): Map[Int, String]= {
val m = list.zipWithIndex.map(_.swap).toMap
val sM = SortedMap(m.toSeq:_*)
sM.+(0 -> "root")
}
After getting the data in the structure required, I loop through the entire collection to structure the data into a path map which would look like
root - twiki - bin - edit - Main - Double_bounce_sender
-rdiff - TWiki - NewUserTemplate
- oops - etc - local - getInterface
type innerMap = mutable.HashMap[String, Any]
def getData(input: RDD[List[String]]): mutable.HashMap[String, innerMap] ={
var mainMap = new mutable.HashMap[String, innerMap]
for(x <- input){
val z: mutable.HashMap[String, innerMap] = storeData(x.toIterator, mainMap ,x(0).toString)
mainMap = mainMap ++ z
}
mainMap
}
def storeData(list: Iterator[String], map: mutable.HashMap[String, innerMap], root: String): mutable.HashMap[String, innerMap]={
list.hasNext match {
case true =>
val v = list.next()
val y = map contains (root) match {
case true =>
println("Adding when exists: "+v)
val childMap = map.get(v).get match {
case _:HashMap[String, Any] => asInstanceOf[mutable.HashMap[String, innerMap]]
case _ => new mutable.HashMap[String, innerMap]
}
val x = map + (v -> storeData(list, childMap, v))
x
case false =>
val x = map + (v -> storeData(list, new mutable.HashMap[String, innerMap], v))
x
}
y.asInstanceOf[mutable.HashMap[String, innerMap]]
case false =>
new mutable.HashMap[String, innerMap]
}
}
The get data method calls each input list and sends it to the storeData method which builds the map.
I'm stuck at two places.
The MainMap(HashMap[String, innerMap]) sent recursively to storeData goes as a new empty map every time.
The second issue is that I'm trying to figure out a way of merging 2 nested Maps that do not have a defined length. Such as merging the maps below.
Map(root -> Map(twiki -> Map(bin -> Map(edit -> Map(Main -> Map(Double -> Map())))))))
Map(root -> Map(twiki -> Map(bin -> Map(rdiff -> Map(TWiki -> Map(NewUser -> Map())))))))
Looking for suggestions on how I could implement this solution and get a final map that contains all the possible paths present in the server log files in one map.
to merge these two maps you can use scalaz and |+| method
# Map("root" ->
Map("twiki" ->
Map("bin" ->
Map("rdiff" ->
Map("TWiki" ->
Map("NewUser" ->
Map.empty[String, String]))))))
res2: Map[String, Map[String, Map[String, Map[String, Map[String, Map[String, Map[String, String]]]]]]] =
Map("root" ->
Map("twiki" ->
Map("bin" ->
Map("rdiff" ->
Map("TWiki" ->
Map("NewUser" -> Map()))))))
# Map("root" ->
Map("twiki" ->
Map("bin" ->
Map("edit" ->
Map("Main" ->
Map("Double" -> Map.empty[String, String]))))))
res3: Map[String, Map[String, Map[String, Map[String, Map[String, Map[String, Map[String, String]]]]]]] =
Map("root" ->
Map("twiki" ->
Map("bin" ->
Map("edit" ->
Map("Main" ->
Map("Double" -> Map()))))))
res2 |+| res3
res4: Map[String, Map[String, Map[String, Map[String, Map[String, Map[String, Map[String, String]]]]]]] =
Map("root" ->
Map("twiki" ->
Map("bin" ->
Map(
"edit" ->
Map("Main" ->
Map("Double" -> Map())),
"rdiff" ->
Map("TWiki" ->
Map("NewUser" -> Map()))))))
Maybe something like this?
scala> type Node = Map[String, Any];
defined type alias Node
scala> def merge( me : Node, you : Node ) : Node = {
| val keySet = me.keySet ++ you.keySet;
| def nodeForKey( parent : Node, key : String ) : Node = parent.getOrElse( key, Map.empty ).asInstanceOf[Node]
| keySet.map( key => (key -> merge( nodeForKey( me, key ), nodeForKey( you, key ) ) ) ).toMap
| }
merge: (me: Node, you: Node)Node
scala> val path1 = Map( "root" -> Map("bin" -> Map("sh" -> Map.empty) ) )
path1: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,scala.collection.immutable.Map[Nothing,Nothing]]]] = Map(root -> Map(bin -> Map(sh -> Map())))
scala> val path2 = Map( "root" -> Map( "bin" -> Map("csh" -> Map.empty), "usr" -> Map.empty ) )
path2: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,scala.collection.immutable.Map[_ <: String, scala.collection.immutable.Map[Nothing,Nothing]]]] = Map(root -> Map(bin -> Map(csh -> Map()), usr -> Map()))
scala> merge( path1, path2 )
res8: Node = Map(root -> Map(bin -> Map(sh -> Map(), csh -> Map()), usr -> Map()))