Generating a Map[String, List[String]] from List[(String,String)] - scala

I got the following list of pairs:
List(("US","New York"),("England","London"),("US","Los Angeles"),("England","Manchester"),("US","Washington"))
I need to generate a Map[Country, List[Cities]]:
Map("US" -> List("New York", "Los Angeles", "Washington"), "England" -> List("London", "Manchester"))
The problem that if I use toMap() directly the values with same keys are removed.
The story so far:
list.groupBy(el => el).map(el => el._1 -> ?)

using groupBy:
list.groupBy(_._1).mapValues(_.map(_._2))
using fold:
list.foldLeft(Map.empty[String, List[String]]) { case (m, (k, v)) =>
m.updated(k, v :: m.getOrElse(k, List()))
}

Related

How to group a list of maps by key Scala 2.11.x

Given the following list of maps (list could be longer):
List(
Map[String,String]("wind"->"none", "rain"->"none", "class"->"on time"),
Map[String,String]("wind"->"none", "rain"->"slight", "class"->"on time"),
Map[String,String]("wind"->"none", "rain"->"slight", "class"->"late"),
...
)
How can I group the maps that I have something like this:
"on time" -> ("wind"->"none", "rain"->"none", "wind"->"none", "rain"->"slight")
"late" -> ("wind"->"none", "rain"->"slight")
I get stuck at working on several maps.
Another option:
val maps =
List(
Map[String, String]("wind" -> "none", "rain" -> "none", "class" -> "on time"),
Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "on time"),
Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "late"),
Map[String, String]("wind" -> "none", "rain" -> "slight")
)
val grouped = maps.foldLeft(Map.empty[String, List[(String, String)]]) {
case (acc, map) if map.contains("class") =>
val key = map("class")
if (acc.contains(key))
acc.updated(key, acc(key) ++ (map - "class").toList)
else
acc + (key -> (map - "class").toList)
case (acc, _) => acc
}
In vanilla Scala 2.12\2.11 assuming the starting point:
val maps =
List(
Map[String, String]("wind" -> "none", "rain" -> "none", "class" -> "on time"),
Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "on time"),
Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "late")
)
you could fold everything:
maps
.filter(_.contains("class")) // guarantee "class" key exists
.map(m => m("class") -> (m - "class").toList)
.foldLeft(Map.empty[String, List[(String, String)]]) {
case (acc, (key, values)) if acc.contains(key) =>
acc.updated(key, acc(key) ++ values)
case (acc, (key, values)) =>
acc + ((key, values))
}
This logic is safe with any input as it will filter out Map[_,_] without class key.
You could remove that .map(..) and do everything in one go in the fold.
If you can guarantee the input is non-empty and contains key class you could use reduceLeft instead of foldLeft and drop the filter.
Bonus the logic is compatible with Iterator[_]:
maps
.iterator
.filter(..)
.map(..)
.foldLeft(..)(..)
}

Handling of Nested Maps

I have a Map which has a key containing another Map.
i.e -
val myDetailsMap = Map("name" -> "abc",
"class" -> "10",
"section" -> "A",
"marksPerSubjectId" -> Map(101 -> "Physics= '70' AND Chemistry='80'",
102 -> "History= '60' AND Civics = '67'"),
"status" -> "pass")
Now, I want to iterate through the marksPerSubjectId key containing another MAP using foreach. How should I proceed ?
On Databricks -
What about using pattern matching?
in Scala 2.13:
myDetailsMap.foreachEntry{ (k, v) =>
v match {
case map: Map[_, _] => map.foreachEntry{ (k, v) => println(v)}
case other => println(other)
}
}
in Scala 2.11:
myDetailsMap.foreach{ case (k, v) =>
v match {
case map: Map[_, _] => map.foreach{ case (k, v) => println(v)}
case other => println(other)
}
}

Filter defined fields of Options in scala

I have a case class with Options:
case class PersonUpdate(name: Option[String], age: Option[Int], country: Option[String])
and I need to check which values are defined and generate a map with its name and values, for example:
if I have this object:
val perUpdate = PersonUpdate(Option("john"), None, Option("England"))
than the map should look like:
val result = Map("people.$.name" -> "john", "people.$.country" -> "England")
what would be the best way do that efficiently the scala way?
For your specific case you can do this:
List(
perUpdate.name.map("people.$.name" -> _),
perUpdate.age.map("people.$.age" -> _.toString),
perUpdate.country.map("people.$.country" -> _)
).flatten.toMap
You can have a more generic solution, but it is not going to be particularly efficient:
perUpdate.getClass.getDeclaredFields.flatMap { f =>
f.setAccessible(true)
f.get(perUpdate).asInstanceOf[Option[Any]].map("people.$."+f.getName -> _.toString)
}.toMap
To extract only certain fields, try this:
val fieldNames = List("name", "age", "country")
fieldNames.flatMap{ fieldName =>
val fieldValue = perUpdate.getClass.getDeclaredField(fieldName)
fieldValue.setAccessible(true)
fieldValue.get(perUpdate).asInstanceOf[Option[Any]].map("people.$."+fieldName -> _.toString)
}.toMap
Case classes are instances of Product, that lets you iterate through their members without reflection:
Seq("name", "age", "country")
.map { "people.$." + _ }
.iterator
.zip(perUpdate.productIterator)
.collect { case (k, Some(v)) => k -> v }
.toMap

Scala convert Map[String, Set[String]] to Map[(String, String), Option[Double]]

I was thinking of a way to create a tuple consisting of the String key from the map along with each of the Strings from the Set together as tuple that form the key in a new map. Value for the new map will be initialized to 0.0.
Ex: If I have to following:
Map[ USA, Set[CA, NY, WA]]
I want to create a new map from this which looks like:
Map[(USA,CA) -> 0.0, (USA,NY) -> 0.0, (USA,WA) -> 0.0]
I am able to create a Map[String, String] but I was hoping to get some help in creating the tuple key.
Map("USA" -> Set("CA", "NY", "WA")) flatMap { case (k, set) => set.map((k, _) -> 0.0) }
val myMap = Map("USA" -> Set("CA", "NY", "WA"))
val newMap = myMap.foldLeft(Map[(String, String), Double]()) {
case (acc, (key, values)) => {
acc ++ (for {
value <- values
} yield (key, value) -> 0.0)
}
}

Dynamically add element to map after pattern matching

I have a method as follows:
protected def extract(implicit params:Params) =
Map(
"address" -> params.address,
"city" -> params.address,
"region" -> params.region,
)collect {
case (k, v) if v.isDefined => k -> v.get
}
I want to substitute value of city such that :
"city" -> if(params.city.contains("York")) "NewYork" else params.city,
How can I achieve this in scala?
You could put it in line
def extract(implicit params:Params) =
Map(
"address" -> params.address,
"city" -> (if(params.city.contains("York")) Some("NewYork") else params.city),
"region" -> params.region
) collect {
case (k, v) if v.isDefined => k -> v.get
}
Im sure there are lots of other way to do it. Not sure what your really after.
Stephens approach of just creating the map with the correct value is probably best. If you've got a general map, of what appears to be String to Option[String], and want to substitute the city key if the value contains York, then this will work as well.
myMap.collect {
case ("city", Some(city)) if(city.contains("York")) => "NewYork"
case (k, v) if v.isDefined => k -> v.get
}
Small correction to Bruce answer
myMap.collect {
case ("city", Some(city)) if(city.contains("York")) => "NewYork"
case (k, v) if v.isDefined => k -> v.get
}
Would not result in a Map, but an Iterable
myMap.collect {
case ("city", Some(city)) if(city.contains("York")) => "city" -> "NewYork"
case (k, Some(v)) => k -> v
}
Would be better