Best way to filter and sort a Map by set of keys - scala

I have a Map instance (immutable):
val source = Map(
("foo", "spam"),
("bar", "hoge"),
("baz", "eggs"),
("qux", "corge"),
("quux", "grault")
)
and I have number of keys (Set or List) in some order which may or may not exist in source map:
baz
foo
quuuuux // does not exist in a source map
But what is the best and cleanest way to iterate over the source map with concise scala style, filter it by my keys and place filtered items into resulting map in the same order as keys are?
Map(baz -> eggs, foo -> spam)
P.S. To clarify - order of keys in resulting map must be the same as in filtration keys list

If you have:
val source = Map(
"foo" -> "spam",
"bar" -> "hoge",
"baz" -> "eggs",
"qux" -> "corge",
"quux" -> "grault"
)
and
val keys = List( "baz", "foo", "quuuux" )
Then, you can:
import scala.collection.immutable.SortedMap
SortedMap(source.toSeq:_*).filter{ case (k,v) => keys.contains(k) }

val keys = List("foo", "bar")
val map = Map("foo" -> "spam", "bar" -> "hoge", "baz" -> "eggs")
keys.foldLeft(ListMap.empty[String, String]){ (acc, k) =>
map.get(k) match {
case Some(v) => acc + (k -> v)
case None => acc
}
}
This will iterate over the keys, building a map containing only the matching keys.
Please note that you need a ListMap to preserve the ordering of keys, although the implementation of ListMap will return the elements in the opposite order they were inserted (since keys are prepended as head of the list)
LinkedHashMap would ensure exact insertion order, but it's a mutable data structure.

If you need an ordered Map, you could use something like a TreeMap with a custom key ordering. So given
import scala.collection.immutable.TreeMap
val source = Map(
("foo", "spam"),
("bar", "hoge"),
("baz", "eggs"),
("qux", "corge"),
("quux", "grault")
)
val order: IndexedSeq[String] = IndexedSeq("baz", "foo", "quuuuux")
implicit val keyOrdering: Ordering[String] = Ordering.by(order.indexOf)
You have choice, either iterate over the ordered keys:
val result1: TreeMap[String, String] = order.collect {
case key if source.contains(key) => key -> source(key)
}(collection.breakOut)
// or a bit shorter
val result2: TreeMap[String, String] = order.flatMap { key => source.get(key).map(key -> _) }(collection.breakOut)
or filter from the source map:
val result3: TreeMap[String, String] = TreeMap.empty ++ source.filterKeys(order.contains)
I am not sure which one would be the most efficient, but I suspect the flatMap one might be fastest, at least for your simple example. Though, imho, the last example is better readable than the others.

Related

Scala map key consists of 2 comma-separated sets. How to extract the first key in a set?

I have a Scala map collection that looks something like this:
var collection = Map((A,B) -> 1)
The key is (A,B) and the value is 1.
My question: If I use collection.head._1, the result is (A,B) which is correct. But I want to extract A only, without B, as I need to compare A with some other variable. So the final result should be A stored in a different variable.
I tried to use collection.head._1(0) which results in error
Any does not take parameters
You can try:
val collection = Map(("A","B") -> 1)
collection.map{ case ((a, b),v) => a -> v}
You can use keySet to get all the keys as a Set[(String, String)] and then map it into the first element of each:
val coll: Map[(String, String), Int] =
Map(
("one", "elephant") -> 1,
("two", "elephants") -> 2,
("three", "elephants") -> 3
)
/*
val myKeys = coll.keySet.map { case (x, _) => x }
// equivalent to:
val myKeys = coll.keySet.map(tup => tup._1)
// equivalent to: */
val myKeys = coll.keySet.map(_._1) // Set(one, two, three)

How to delete key from Map in Option Scala

Say i have case classes like this.
case class someClass0(content: someClass1)
case class someClass1(someContent: Option[Map[String, someClass2]])
case class someClass2(someKey: Array[Int])
I need to delete items in Map(which is immutable) by values.
This values i get through iteration.
val keys_to_remove = new ListBuffer[String]()
val keys_to_keep: List[Int] = List(100, 200)
for (x <- keys_to_keep) {
content.someContent.get.foreach {
case (key: String, value: someClass2) => {
if (!value.someKey.contains(x)) {
keys_to_remove.append(key)
}
}
}
}
So, how to keep all the structure, and delete only needed items by key?
I was trying to change type of Map like
content.someContent.map(_.to(collection.mutable.Map))
But content.someContent.get.remove(key) is not working.
What am i doing wrong?
You don't need mutability for that.
val keys_to_keep: List[String] = List("a", "b")
val res = content.someContent.map(
_.filterKeys(k => !keys_to_keep.contains(k))
)
filterKeys filters a Map by testing each entries' key against a condition.
Of course, it is important to remember that you can't test contains on a List[Int] against Strings, as the result will always be false.
Furthermore, try looking up style-guides for Scala:
Classes are usually named in upper camel case
Values and variables are usually named in lower camel case
Try it out
You can do it using - operator and foldLeft on keys to remove.
you are using get for get value, if you want do it safety, you need to use:
content.someContent.map(immutableMap =>
keys_to_remove.foldLeft(immutableMap){
(map, key) =>
map - key
}).getOrElse(Map.empty[String, SomeClass2])
this works like in this example:
import scala.collection.mutable.ListBuffer
val immutableMap = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4)
val keys_to_remove: ListBuffer[String] = ListBuffer("b", "d")
println(immutableMap) // Map(a -> 1, b -> 2, c -> 3, d -> 4)
val mapWithoutKeys = keys_to_remove.foldLeft(immutableMap){
(map, key) =>
map - key
}
println(mapWithoutKeys) //Map(a -> 1, c -> 3)
Here's how you can do it:
val optionalMap = someClass0.content.someContent.map {
contentMap => contentMap - keyToBeRemoved
}
val originalStructure = someClass0.copy(content = SomeClass1(optionalMap))
Here's the Scastie
This will remove all keys and keep structure
val someClass0_copy = someClass0.copy(content = Content(someContent = someClass0.content. someContent.map(_.removedAll(keysToRemove)))

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.

Scala Map - Use map function to replace key->value

I want to change the keys and values for the keys key1 and key2 only when their values are val1 and val2 (both these mappings should be present for the transformation to take place). I am able to do it using the following code, but I do not think this is very elegant or efficient.
Is there a better way to do the same thing, perhaps using just one .map function applied over map?
Code:
val map = Map(
"key1" -> "val1",
"key2" -> "val2",
"otherkey1" -> "otherval1"
)
val requiredKeys = List("key1", "key2")
val interestingMap = map.filterKeys(requiredKeys.contains) // will give ("key1" -> "val1", "key2" -> "val2").
val changedIfMatched =
if (interestingMap.get("key1").get.equalsIgnoreCase("val1") && interestingMap.get("key2").get.equalsIgnoreCase("val2"))
Map("key1" -> "newval1", "key2" -> "newval2")
else
interestingMap
print(map ++ changedIfMatched) // to replace the old key->values with the new ones, if any.
Also can ++ operation to update the old key->value mappings be made more efficient?
Just do the check ahead of time:
map
.get("k1").filter(_.equalsIgnoreCase("v1"))
.zip(map.get("k2").filter(_.equalsIgnoreCase("v2")))
.headOption
.fold(map) { _ =>
map ++ Map("key1" -> "newVal1", "key2" -> "newVal2")
}
Here's an approach that checks that both key value pairs match.
EDIT: Added a mapValues method to the Map class. This technique can be used to do further checks on the values of the map.
val m = Map("key1" -> "val1", "key2" -> "VAL2", "otherkey1" -> "otherval1")
val oldKVs = Map("key1" -> "val1", "key2" -> "val2")
val newKVs = Map("newkey1" -> "newval1", "newkey2" -> "newval2")
implicit class MapImp[T,S](m: Map[T,S]) {
def mapValues[R](f: S => R) = m.map { case (k,v) => (k, f(v)) }
def subsetOf(m2: Map[T,S]) = m.toSet subsetOf m2.toSet
}
def containsKVs[T](m: Map[T,String], sub: Map[T,String]) =
sub.mapValues(_.toLowerCase) subsetOf m.mapValues(_.toLowerCase)
val m2 = if (containsKVs(m, oldKVs)) m -- oldKVs.keys ++ newKVs else m
println(m2)
// Map(otherkey1 -> otherval1, newkey1 -> newval1, newkey2 -> newval2)
It takes advantage of the fact that you can convert Maps into Sets of Tuple2.
I think this will be the most generic and resuable solution for the problem.
object Solution1 extends App {
val map = Map(
"key1" -> "val1",
"key2" -> "val2",
"otherkey1" -> "otherval1"
)
implicit class MapUpdate[T](map: Map[T, T]) {
def updateMapForGivenKeyValues: (Iterable[(T, T)], Iterable[(T, T)]) => Map[T, T] =
(fromKV: Iterable[(T, T)], toKV: Iterable[(T, T)]) => {
val isKeyValueExist: Boolean = fromKV.toIterator.forall {
(oldKV: (T, T)) =>
map.toIterator.contains(oldKV)
}
if (isKeyValueExist) map -- fromKV.map(_._1) ++ toKV else map
}
}
val updatedMap = map.updateMapForGivenKeyValues(List("key1" -> "val1", "key2" -> "val2"),
List("newKey1" -> "newVal1", "newVal2" -> "newKey2"))
println(updatedMap)
}
So the method updateMapForGivenKeyValues takes the List of old key value and new key value tuple. If all the key value pairs mentioned in the first parameter of the method exist in the map then only we will update the map with new key value pairs mentioned in the second parameter of the method. As the method is generic will can be used on any data type like String, Int, some case class etc.
we can easily re-use the method for different type of maps without even changing a single line of code.
Answer to modified question
val map = Map(
"key1" -> "val1",
"key2" -> "val2",
"otherkey1" -> "otherval1"
)
val requiredVals = List("key1"->"val1", "key2"->"val2")
val newVals = List("newval1", "newval2")
val result =
if (requiredVals.forall{ case (k, v) => map.get(k).exists(_.equalsIgnoreCase(v)) }) {
map ++ requiredVals.map(_._1).zip(newVals)
} else {
map
}
This solution use forall to check that all the key/value pairs in requiredKeys are found in the map by testing each pair in turn.
For each key/value pair (k, v) it does a get on the map using the key to retrieve the current value as Option[String]. This will be None if the key is not found or Some(s) if the key is found.
The code then calls exists on the Option[String]. This method will return false if value is None (the key is not found), otherwise it will return the result of the test that is passed to it. The test is _.equalsIgnoreCase(v) which does a case-insensitive comparison of the contents of the Option (_) and the value from the requireKeys list (v).
If this test fails then the original value of map is returned.
If this test succeeds then a modified version of the map is return. The expression requiredVals.map(_._1) returns the keys from the requireVals list, and the zip(newVals) associates the new values with the original keys. The resulting list of values is added to the map using ++ which will replace the existing values with the new ones.
Original answer
val map = Map(
"key1" -> "val1",
"key2" -> "val2",
"otherkey1" -> "otherval1"
)
val requiredVals = Map("key1"->"val1", "key2"->"val2")
val newVals = Map("newkey1" -> "newval1", "newkey2" -> "newval2")
val result =
if (requiredVals.forall{ case (k, v) => map.get(k).exists(_.equalsIgnoreCase(v)) }) {
map -- requiredVals.keys ++ newVals
} else {
map
}
Note that this replaces the old keys with the new keys, which appears to be what is described. If you want to keep the original keys and values, just delete "-- requiredVals.keys" and it will add the new keys without removing the old ones.
You can use the following code:
val interestingMap =
if(map.getOrElse("key1", "") == "val1" && map.getOrElse("key2", "") == "val2")
map - "key1" - "key2" + ("key1New" -> "val1New") + ("key2New" -> "val2New")
else map
The check part(if statement) can be tweaked to suit your specific need.
if any of these key-value pairs are not present in the map, the original map will be returned, otherwise, you will get a new map with two updates at the requested keys.
Regarding efficiency, as long as there are only two keys to be updated, I do not think there is a real performance difference between using + to add elements directly and using ++ operator to overwrite the keys wholesale. If your map is huge though, maybe using a mutable map proves to be a better option in the long run.

scala: union of two maps whose key type is the same and whose value type is a collection of elements, but whose types are different

I would like to create a union of two maps whose key type is the same and whose value type is a collection of elements, but whose types are different.
Consider the following contrived example:
case class Child(name: String)
val peopleToChildren: Map[String, Seq[Child]] =
Map("max" -> Seq(Child("a"), Child("b")),
"yaneeve" -> Seq(Child("y"), Child("d")))
case class Pet(name: String)
val peopleToPets: Map[String, Seq[Pet]] =
Map("max" -> Seq(Pet("fifi")),
"jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
// people may have children
// people may have pets
// would like a map from people to a tuple with a potentially empty list of children and a
// potentially empty list of pets
// ???
}
What would be a way to do it which is concise, idiomatic, but still legible?
I found no single function that can do that in the standard scala collections library.
Proposed solutions can be based solely on the standard library, or propose an external solution.
I post it here since I could not easily find an online solution to a seemingly standard operation.
This appears to work.
val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
(peopleToChildren.keySet ++ peopleToPets.keySet).map { k =>
k -> (peopleToChildren.getOrElse(k, Seq())
,peopleToPets.getOrElse(k, Seq()))
}.toMap
}
Get all the keys. For every key do a getOrElse() on each of the feeder Maps.
Just for the curious, here's how it could be done using Scalaz:
import scalaz._, Scalaz._
case class Child(name: String)
val peopleToChildren = Map(
"max" -> List(Child("a"), Child("b")),
"yaneeve" -> List(Child("y"), Child("d"))
)
case class Pet(name: String)
val peopleToPets = Map(
"max" -> List(Pet("fifi")),
"jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))
)
val peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] =
peopleToChildren.strengthR(nil[Pet]) |+| peopleToPets.strengthL(nil[Child])
Explanation:
nil[Pet] is just an alias for List.empty[Pet]
strengthR for a given Functor tuples contained values, so that its parameter is at the right. Here it is equivalent to peopleToChildren.mapValues(v => (v, nil[Pet]))
strengthL is the same, but element will be added to the left
|+| is an append operator for a given Semigroup. The one here is derived recursively:
for Map[K, V], it uses |+| to combine values of type V if a given key exists in both Maps. If the value is only present in one of them, it will be retained as is. Here, V = (List[Child], List[Pet])
for tuples (A, B), it again uses |+| to combine both As and Bs. Here, A = List[Child] and B = List[Pet]
for lists of any type (as well as strings, vectors or streams) it does concatenation. This is why I had to change type of Map values to be Lists - for generic Seqs this operation is not defined
Result:
peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] = Map(
"max" -> (List(Child("a"), Child("b")), List(Pet("fifi"))),
"jill" -> (
List(),
List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))
),
"yaneeve" -> (List(Child("y"), Child("d")), List())
)
To answer my own question, the following is the way that I solved it, but it seems overly long and complex:
Welcome to the Ammonite Repl 1.0.2
(Scala 2.11.11 Java 1.8.0_91)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
# case class Child(name: String)
defined class Child
# val peopleToChildren: Map[String, Seq[Child]] =
Map("max" -> Seq(Child("a"), Child("b")),
"yaneeve" -> Seq(Child("y"), Child("d")))
peopleToChildren: Map[String, Seq[Child]] = Map("max" -> List(Child("a"), Child("b")), "yaneeve" -> List(Child("y"), Child("d")))
#
# case class Pet(name: String)
defined class Pet
# val peopleToPets: Map[String, Seq[Pet]] =
Map("max" -> Seq(Pet("fifi")),
"jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
peopleToPets: Map[String, Seq[Pet]] = Map("max" -> List(Pet("fifi")), "jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
#
# val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
// people may have children
// people may have pets
// would like a map from people to a tuple with a potentially empty list of children and a
// potentially empty list of pets
val paddedPeopleToChildren = peopleToChildren.map{ case (person, children) => person -> (children, List.empty[Pet])}
val paddedPeopleToPets = peopleToPets.map{ case (person, pets) => person ->(List.empty[Child], pets)}
val notGoodEnough = paddedPeopleToPets ++ paddedPeopleToChildren // this is here to show that it does not work since it overwrites the value of a key - Map(max -> (List(Child(a), Child(b)),List()), jill -> (List(),List(Pet(bobo), Pet(jack), Pet(Roger rabbit))), yaneeve -> (List(Child(y), Child(d)),List()))
val allSeq = paddedPeopleToPets.toSeq ++ paddedPeopleToChildren.toSeq
val grouped = allSeq.groupBy(_._1).mapValues(_.map { case (_, tup) => tup })
val solution = grouped.mapValues(_.unzip).mapValues {case (wrappedChildren, wrappedPets) => (wrappedChildren.flatten, wrappedPets.flatten)}
solution
}
peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = Map(
"yaneeve" -> (ArrayBuffer(Child("y"), Child("d")), ArrayBuffer()),
"max" -> (ArrayBuffer(Child("a"), Child("b")), ArrayBuffer(Pet("fifi"))),
"jill" -> (ArrayBuffer(), ArrayBuffer(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
)