Scala - Convert a List of Maps to a Map - scala

I have a JSON string that I need to process and I am mapping it in the following manner:
val jsonMap = mapper.readValue[Map[String, Object]](jsonString)
My jsonMap contains the following value:
Map(k1 -> List(Map(k2 -> v2), Map(k3 -> v3))
The desired value for newJsonMap is:
Map(k1 -> Map(k2 -> v2))
So in a nutshell, I want to convert the value of the k1 key from a List to a Map. I first started to implement according to this question: Scala - Flatten a List of Maps to Map
But then I realized that I was dealing with different datatypes, in this case Map[String, Object] which means I can't perform operations such as using the scala in-built flatten method to lists.
Is there any way I can convert this Map into a Scala Map so I can apply the necessary transformations to the JSON? Any thoughts on how I should proceed?
Thanks in advance.

The simple answer is to do this
val jsonMap = mapper.readValue[Map[String, List[Map[String, String]]]](jsonString)
and then use the answer from the previous question.
If you want to do it by hand, something like this should work:
val map: Map[String, Map[String, String]] =
jsonMap.collect {
case (k, v: List[_]) =>
val list: List[Map[String, String]] =
v.collect {
case m: Map[_, _] =>
m.collect {
case (k: String, v: String) =>
k -> v
}
}
k -> list.headOption.getOrElse(Map.empty)
}
This will ignore any elements in Object that are not the required type. Nested collect expressions are required because type erasure prevents matching on a nested type in one operation.
This is assuming that you want the head of the List[Map] inside the Object, but it should be clear how to change this to a different transformation.

Related

converting a list to map in scala

I am trying to convert a list to map in scala.
Input
val colNames = List("salary_new", "age_new", "loc_new")
Output
Map(salary_new -> salary, age_new -> age, loc_new -> loc)
Following code is working, but seems like I am over killing it.
val colRenameMap = colNames.flatMap(colname => Map(colname -> colname.split("_")(0))).toMap
I think map instead of flatMap would be more suitable for your case. Also you don't need to use the Map type internally, a single tuple should do the job.
For the sake of completeness this is how the definition of toMap looks like:
toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U]
as you can see the method expects a (T, U) which is a Tuple2.
Finally, two options using map:
// option 1: key/value
colNames.map{c => c -> c.split("_")(0)}.toMap
// option 2: tuple
colNames.map{c => (c, c.split("_")(0))}.toMap

Convert List[(Int,String)] into List[Int] in scala

My goal is to to map every word in a text (Index, line) to a list containing the indices of every line the word occurs in. I managed to write a function that returns a list of all words assigned to a index.
The following function should do the rest (map a list of indices to every word):
def mapIndicesToWords(l:List[(Int,String)]):Map[String,List[Int]] = ???
If I do this:
l.groupBy(x => x._2)
it returns a Map[String, List[(Int,String)]. Now I just want to change the value to type List[Int].
I thought of using .mapValues(...) and fold the list somehow, but I'm new to scala and don't know the correct approach for this.
So how do I convert the list?
Also you can use foldLeft, you need just specify accumulator (in your case Map[String, List[Int]]), which will be returned as a result, and write some logic inside. Here is my implementation.
def mapIndicesToWords(l:List[(Int,String)]): Map[String,List[Int]] =
l.foldLeft(Map[String, List[Int]]())((map, entry) =>
map.get(entry._2) match {
case Some(list) => map + (entry._2 -> (entry._1 :: list))
case None => map + (entry._2 -> List(entry._1))
}
)
But with foldLeft, elements of list will be in reversed order, so you can use foldRight. Just change foldLeft to foldRight and swap input parameters, (map, entry) to (entry, map).
And be careful, foldRight works 2 times slower. It is implemented using method reverse list and foldLeft.
scala> val myMap: Map[String,List[(Int, String)]] = Map("a" -> List((1,"line1"), (2, "line")))
myMap: Map[String,List[(Int, String)]] = Map(a -> List((1,line1), (2,line)))
scala> myMap.mapValues(lst => lst.map(pair => pair._1))
res0: scala.collection.immutable.Map[String,List[Int]] = Map(a -> List(1, 2))

Scala - pattern head/tail on Map

I'm trying to make a tail recursive method but i'm using Map and i don't know how to use Pattern Matching to check if Map is empty/null and get head/tail:
def aa(a:Map[String, Seq[Operation]]): Map[String, (Seq[Operation], Double)] = {
def aaRec(xx:Map[String, Seq[Operation]],
res:Map[String, (Seq[Operation], Double)],
acc:Double = 0): Map[String, (Seq[Operation], Double)] = xx match {
case ? =>
res
case _ =>
val head = xx.head
val balance = head._2.foldLeft(acc)(_ + _.amount)
aaRec(xx.tail, res + (head._1 -> (head._2, balance)), balance)
}
aaRec(a, Map[String, (Seq[Operation], Double)]())
}
}
What is the correct syntax on case empty map and case h :: t?
Thank's in advance
Map has no order so it has no head or tail. It also has no unapply/unapplySeq method so you can't do pattern matching on a Map.
I think going with a foldLeft might be your best option.
I'm not sure if it's possible to pattern match on a map, but this code could be rewritten using basic combinator methods:
def aa(a:Map[String, Seq[Operation]]): Map[String, (Seq[Operation], Double)] =
a.mapValues(seq => (seq, seq.map(_.amount).sum))

scala: type equality of two variables

I have two Map[String, T]s, where T is an instance of subtype of Fruit. I need to construct new Map from two Maps, where the key is the common key names from the two maps, and the value is the Seq[Fruit] iff the values from the two maps shares the same type.
class Fruit
case class Apple() extends Fruit
case class Banana(num: Int) extends Fruit
case class Orange() extends Fruit
For example, if I have following two maps:
val map1 = Map("first" -> Apple(),
"second" -> Banana(3),
"third" -> Orange())
val map2 = Map("first" -> Orange(),
"second" -> Banana(4),
"third" -> Orange())
I need the result map, map3 which has following members:
generateMap(map1: Map[String, Fruit], map2: Map[String, Fruit]): Map[String, Seq[Fruit]]
=> results a map look like
Map("second" -> Seq(Banana(3), Banana(4)),
"third" -> Seq(Orange(), Orange())
I'm not sure how to write a function, generateMap. Could anyone help me to implement that? (using Scala 2.11.x)
Note that the class definitions (Fruits and others) are fixed, so I cannot modify them.
scala> val r: Map[String, Seq[Fruit]] = (map1.toList ++ map2.toList).
groupBy(x => x._1).
mapValues(lst => lst.map(x => x._2)).
.filter {
case (key, lst) => lst.forall(x =>
x.getClass == lst.head.getClass)
}
r: Map[String, Seq[Fruit]] = Map(third -> List(Orange(), Orange()),
second -> List(Banana(3), Banana(4)))
val m3 = (map1.toSeq ++ map2.toSeq). // Combine the maps
groupBy (x=>x._1). //Group by the original keys
map{case (k,lst)=> (k, lst.map(x=> x._2))}. //Strip the keys from the grouped sequences
filter{case (_, lst) => lst.forall(i => lst.head.getClass == i.getClass)}. //Filter out hetergeneous seqs
toMap // Make a map
Without forall:
(map1.toList ++ map2.toList).groupBy(_._1).mapValues(_.map(_._2))
.filter(_._2.map(_.getClass).toSet.tail.isEmpty)
Map(third -> List(Orange(), Orange()), second -> List(Banana(3), Banana(4)))
This version requires a little more (but still linear inside filter) CPU and memory than version with forall, so you should use it only for small collections.

max function on list do not work in scala?

There is a snippet of scala code,which I think quite easy
val m1 = Map("age"->60,"name"->"x")
val m2 = Map("age"->99,"name"->"j")
val l = List(m1,m2)
val max = l.maxBy(_("age"))
However, instead of the expecting result
val m2 = Map("age"->99,"name"->"j")
I get an error:
<console>:13: error: No implicit Ordering defined for Any.
I know there is something wrong about the implicit parameter,but I don't know how to solve this problem.
update
further,suppose I need a more general solution for this,a function
def max(l:List[Map[String,Any]],key:String)
then
max(l,"age") == Map("age"->99,"name"->"j")
max(l,"name") == Map("age"->60,"name"->"x")
Your maps have type Map[String, Any] so compiler could not find Ordering for Any object. Add explicit conversion to Int:
val max = l.maxBy(_("age").asInstanceOf[Int])
Or try use Map with specific value's type such as Map[String, Int] or similar.
Other way is to use case classes or tuples:
val list = List((60, "j"), (99, "x"))
list.maxBy(_._1)
This works,
l.maxBy(m => m("age").toString.toInt)
and is closer to intended semantics on maximal (numerical) age, even that it does not prove type-safe, the original maps type preserves no information on the intended types, as aforementioned.
Try Using last line as
val max = l.maxBy(_("age").toString.toInt)
Something like this may work (not that I like the idea of Map[String, Any]):
def myOrder(k: String) = {
x: Map[String, Any] =>
x(k) match { case i:Int => (i, "")
case s:String => (0, s) }
}
l.maxBy(myOrder("age")) // Map(age -> 99, name -> j)
l.maxBy(myOrder("name")) // Map(age -> 60, name -> x)