How to find a map in list of maps where a key has a certain value in Scala? - scala

I have a List[Map[String, Any] and I want to return the map which has a certain value for one of the keys. Here's an obfusticated example:
val items: List[Map[String, Any]] = List(Map("name" -> "A", "size" -> 50), Map("name" -> "B", "size" -> 75), Map("name" -> "C", "size" -> 100)))
val mapB: Map[String, Any] = items.find(|m| m("name") == "B") // Psuedo code
mapB would be Map("name" -> "B", "size" -> 75).
Bonus Question
Is there also a way to return the value of another key instead of the whole Map?
For example, if I only wanted the size value (75) for name B? I know I can extract it from the Map as a two-step operation, but if there's another more idiomatic way, I'd be interested in learning it.
Thanks!

find() returns an Option in case what you're looking for cannot be found.
val mapB: Option[Map[String, Any]] = items.find(_("name") == "B") //safe
or
val mapB: Map[String, Any] = items.find(_("name") == "B").get //unsafe, might throw
To retrieve the size value from the first Map where "name" -> "B" you could do something like this.
val sz :Any = items.find(m => m.get("name").contains("B")
&& m.get("size").nonEmpty).fold(0:Any)(_("size"))
Note that sz is type Any because that's what the type of the Map values. Which means that you'd have to cast it (discouraged) to type Int before you can do anything useful with it.
Avoid type Any. It's a sure sign that you're going in the wrong direction.

Complementing jwvh's answers (and using Tim's suggestion to improve safety).
Here is a function that addresses your bonus question.
def findAndGet(maps: List[Map[String, Any]], condition: (String, Any), key: String): Option[Any] = condition match {
case (k, v) =>
maps.find(map => map.get(k).contains(v)).flatMap(map => map.get(key))
}
findAndGet(maps = items, condition = ("name", "B"), key = "size")
// res0: Option[Any] = Some(75)
BTW, a Map[String, Any] makes me thing you are dealing with JSONs, have you take a look to any of the Scala libraries for dealing with them in a more "typesafe" way?, having an Any in your code should always be an alarm that something is wrong.

Related

how to access key values within nested map in Scala

I have a List of Maps. One of the maps has another map inside it (2 level deep). I need to access some of keys from the inner most map and finally change the values. The issue I'm facing is to retrieve the keys from the inner most map. I'm very new to Scala and tried different things without luck.
I have flatten the List to Map and then tried to retrieve the key, values. The thing is, I can print the entire inner map, but not sure how to iterate thru that.
Below is the code: at a very basic, I would like to retrieve the values corresponding to keys from innermost map; say for the keys "isStudentLoankRequested", "schoolStructure".
object ListToMaps {
def main(args: Array[String]) {
val dataInputKeysListvar = List(Map("identityKeyName" -> "MY_ID", "identityKeyValue" -> "abcd-1234-xyz"),
Map("identityKeyName" -> "OUR_ID", "identityKeyValue" -> "1234567890",
"identityInformation" -> Map("writeFrequency" -> "NEVER", "studentStatus" -> "SEP", "annualValue" -> 0,
"schoolStructure" -> "OTHER", "studentType" -> "FTS", "occupationDescription" -> "other",
"studentAccountBalanceRange" -> "string", "isStudentLoankRequested" -> "N", "schoolName" -> "myschool",
"parentsIncome" -> 100)),
Map("identityKeyName" -> "FINAL_DECISION_KEY", "identityKeyValue" -> "0000-ABCD-4567-IJKL"))
val x = dataInputKeysListvar.flatten.toMap
val y = x("identityInformation")
if (x.contains("identityInformation")){
println("value of y is" + y)
}
}
}
As you can see from the print stmt, I can print the entire map of the inner most map, but need help in terms of iterating thru that.
If you know at compile time which fields and values you need to change.
You can hard code the logic, like this:
def fixData(data: List[Map[String, Any]]): List[Map[String, Any]] =
data.map { outerMap =>
outerMap.get("identityInformation") match {
case Some(innerMap) =>
// Put as many key pairs you want to change.
// Note: if the key does not exists it will be added!
val updatedInnerMap = innerMap.asInstanceOf[Map[String, String]] ++ Map(
"isStudentLoankRequested" -> "No"
)
outerMap + ("identityInformation" -> updatedInnerMap)
case None =>
outerMap
}
}
If the key-values to change are dynamic, and / or some inner keys may not exists, or if the level of nesting can go on.
You can do something like this:
def updateMap(map: Map[String, Any], updates: Map[String, Any]): Map[String, Any] =
map.map {
case (key, value: Map[_, _]) =>
updates.get(key) match {
case Some(innerUpdates : Map[_, _]) =>
key -> updateMap(
map = value.asInstanceOf[Map[String, Any]],
updates = innerUpdates.asInstanceOf[Map[String, Any]]
)
case Some(newValue) =>
key -> newValue
case None =>
key -> value
}
case (key, value) =>
key -> updates.getOrElse(key, default = value)
}
def fixData(data: List[Map[String, Any]], updates: Map[String, Any]): List[Map[String, Any]] =
data.map(outerMap => updateMap(outerMap, updates))
Note: The above snippets use "unsafe" techniques like asInstanceOf because we lost type safety the moment you got a Map[String, Any]. Always that I see such structure, I think of JSON. I would suggest you to use an appropriate library for managing such kind of data, like circe, instead of writing code as the above.

Correct Approach to Recursively Summing Map in Scala

I have just started a project in work where we are migrating some C# tooling across to a new Scala project. This is my first exposure to the language (and functional programming in general) so in the interest of not just writing Java style code in Scala, I am wondering what the correct approach to handling the following scenario is.
We have two map objects which represent tabular data with the following structure:
map1 key|date|mapping val
map2 key|number
The mapping value in the first object is not always populated. Currently these are represented by Map[String, Array[String]] and Map[String, Double] types.
In the C# tool we have the following approach:
Loop through key set in first map
For every key, check to see if the mapping val is blank
If no mapping then fetch the number from map 2 and return
If mapping exists then recursively call method to get full range of mapping values and their numbers, summing as you go. E.g. key 1 might have a mapping to key 4, key 4 might have a mapping to key 5 etc and we want to sum all of the values for these keys in map2.
Is there a clever way to do this in Scala which would avoid updating a list from within a for loop and recursively walking the map?
Is this what you are after?
#annotation.tailrec
def recurse(key: String, count: Double, map1: Map[String, String], map2: Map[String, Double]): Double = {
map1.get(key) match {
case Some(mappingVal) if mappingVal == "" =>
count + map2.getOrElse(mappingVal, 0.0)
case Some(mappingVal) =>
recurse(mappingVal, count + map2.getOrElse(mappingVal, 0.0), map1, map2)
case None => count
}
}
example use:
val m1: Map[String, String] = Map("1" -> "4", "4" -> "5", "5" -> "6", "8" -> "")
val m2: Map[String, Double] = Map("1" -> 1.0, "4" -> 4.0, "6" -> 10.0)
m1.map {
case (k, _) => k -> recurse(k, 0.0, m1, m2)
}.foreach(println)
Output:
(1,14.0)
(4,10.0)
(5,10.0)
(8,0.0)
Note that there is no cycle detection - this will never terminate if map1 has a cycle.

Handle Any data type dynamically in Scala

I have a map in Scala returned by a function which is of type Map[String, Any]
For example:
val map: Map[String, Any] = Map("key1" -> "strVal", "key2" -> List[Map[String, Any]](), "key3" -> Map("k1" -> "v1"))
Now the problem is, to work on the value I get corresponding to a key, I've to use asInstanceOf[] every time. For eg,
val key2Hash = map.getOrElse("key3", Map()).getOrElse("k1", "")
throws error because the Map retrieved is of form Any and I have to use asInstanceOf[] for every situation as belows:
val key2Hash = map.getOrElse("key3", Map()).asInstanceOf[Map[String, String]].getOrElse("k1", "")
Is there a better way to do it? Or should I not be starting of with Map[String, Any] at the first place?
Map[String, Any]? You might as well use python directly!
Joking apart, you can get "nicer" casts syntax using pattern matching:
map.get("key3") match {
case Some(anotherMap: Map[String, String]) => ...
case _ => ...
}

How to denormalize a nested Map in Scala, with the denormalized string containing only values?

I am new to Scala and FP in general and wanted to know how a nested collection can be denormalized in Scala. For example, if we had data of a question paper for an exam as:
Map("subject" -> "science", "questionCount" -> 10, "questions" ->
Map("1" ->
Map("ques" -> "What is sun?", "answers" ->
Map("1" -> "Star", "2" -> "Giant bulb", "3" -> "planet")
), "2" ->
Map("ques" -> "What is moon?", "answers" ->
Map("1" -> "Giant rock", "2" -> "White lamp", "3" -> "Planet")
)
)
)
When denormalized into strings, using only the values, it can be written as:
science,2,1,What is sun?,Star
science,2,1,What is sun?,Giant bulb
science,2,1,What is sun?,planet
science,2,2,What is moon?,Giant rock
science,2,2,What is moon?,White lamp
science,2,2,What is moon?,Plane
I understand that I can use map to process each item in a collection, it returns exactly the same number of items. Also, while flatMap can be used to process each item into multiple items and return more items than in the collection, I am unable to understand how I can do the denormalization using it.
Also, is it possible to do a partial denormalization like this?
science,2,1,What is sun?,[Star,Giant bulb,planet]
science,2,2,What is moon?,[Giant rock,White lamp,Planet]
Working with nested maps of type Map[String, Any] will never be pretty because you always have to cast the nested values.
If you want a String for every answer, you can flatMap over the questions and then map over the answers.
def normalize(map: Map[String, Any]): Seq[String] = {
val subject = map("subject")
val questions = map("questions").asInstanceOf[Map[String, Any]]
val size = questions.size // or map("questionCount") ?
val lines = questions.flatMap { case (id, q) =>
val question = q.asInstanceOf[Map[String, Any]]
val answers = question("answers").asInstanceOf[Map[String, Any]]
answers.values.map(answer =>
List(subject, size, id, question("ques"), answer) mkString ","
)
}
lines.toSeq
}
If you want a String per question (partial denormalization), you can just map over the questions.
def seminormalize(map: Map[String, Any]): Seq[String] = {
val subject = map("subject")
val questions = map("questions").asInstanceOf[Map[String, Any]]
val size = questions.size // or map("questionCount") ?
val lines = questions.map { case (id, q) =>
val question = q.asInstanceOf[Map[String, Any]]
val answers = question("answers").asInstanceOf[Map[String, Any]]
List(subject, size, id, question("ques"), answers.values.mkString("[", "," , "]")) mkString ","
}
lines.toSeq
}
We could use normalize and seminormalize as :
scala> normalize(map).foreach(println)
science,2,1,What is sun?,Star
science,2,1,What is sun?,Giant bulb
science,2,1,What is sun?,planet
science,2,2,What is moon?,Giant rock
science,2,2,What is moon?,White lamp
science,2,2,What is moon?,Planet
scala> seminormalize(map).foreach(println)
science,2,1,What is sun?,[Star,Giant bulb,planet]
science,2,2,What is moon?,[Giant rock,White lamp,Planet]
In the question entries of Map used differently:
Here they have to be sequences: Map("subject" -> "science", "questionCount" -> 10, ...
Here they have to create different rows: Map("1" -> "Star", "2" -> "Giant bulb",...
So, first I suggest to introduce notion of Sequence and notion of Choice
Then I would implement denormalization as follows:
object Runner3 extends App {
case class Sequence(items: List[(String, Any)])
case class Choices(items: List[Any])
val source = Sequence(List(
"subject" -> "science",
"questionCount" -> 2,
"questions" -> Choices(List(
Sequence(List(
"ques" -> "What is sun?",
"answers" -> Choices(List("Star", "Giant bulb", "planet")))),
Sequence(List(
"ques" -> "What is moon?",
"answers" -> Choices(List("Giant rock", "White lamp", "Planet"))))))))
def denormalize(seq: Sequence): List[List[String]] =
seq.items.map {
case (_, choices: Choices) => denormalize(choices)
case (_, otherValue) => List(List(otherValue.toString))
}.reduceLeft(
(list1: List[List[String]], list2: List[List[String]]) =>
for(item1 <- list1; item2 <- list2) yield item1 ::: item2
)
def denormalize(choices: Choices): List[List[String]] =
choices.items.flatMap {
case seq: Sequence => denormalize(seq)
case otherValue => List(List(otherValue.toString))
}
denormalize(source).foreach(line => println(line.mkString(",")))
}

correct use of scala Map keyset

I am fussing about how to derive a key from a Scala Map.
The Map in question is simple:
The following function to derive a key from a Scala Map is here under:
def extractKey(myMap: Map[String, String]): String {
//process myMap
myMap = Map("SSN" -> "1")
//return "SSN"
}
val myMap = Map("Visa Number" "10")
How do I extract the string Visa Number from it?
Okay, I tried this much so far:
myMap.keySet and I derived a Set out of it as: scala.collection.immutable.Set[String] = Set("Visa Number")
I am a little confused about to proceed.
I tried the following:
myMap.keys
this returned a Iterable[String] = Set("Visa Number")
I also tried the following:
myMap.keysIterator that returned an Iterator[String]
So, what is the best way to derive a key out of the Map myMap
Now, what if I had a longer Map as:
myMapLonger = Map("SSN" -> "10", "AMEX" -> 11)
then how would i capture the keys and put them into a List?
thanks
Map("SSN" -> "10", "AMEX" -> 11).keys.toSeq
or
Map("SSN" -> "10", "AMEX" -> 11).toSeq.map(_._1)
One approach invloves applying unzip on the given Map,
val (keys,values) = Map("SSN" -> "10", "AMEX" -> 11).unzip
which delivers a duple of lists, the first entry with the keys, the second with the values
keys: List(SSN, AMEX)
values: List(10, 11))