Scala For Comprehension with Filter - scala

I am using Scala's for comprehension to produce a modified facetFilter. If a value in facetFilter doesn't exist in allFacets, it should be filtered out. Currently, the newFacetFilter doesn't filter at all.
val allFacets = Map(
"band_material" -> Map("Rubber" -> 11),
"dial_color" -> Map("Ivory" -> 68, "Salmon"-> 3))
val facetFilter =
Map("band_material" -> List("Yellow Gold Plated", "Rubber"),
"dial_color" -> List("Ivory"))
val newFacetFilter =
for {
(k,v) <- allFacets
(facetName, facetArr) <- facetFilter
aFacet <- facetArr
if k != facetName || !v.contains(aFacet)
} yield (facetName -> facetArr)
Current Output of newFacetFilter:
Map("band_material" -> List("Yellow Gold Plated", "Rubber"), "dial_color" -> List("Ivory"))
Expected Output of newFacetFilter:
Map("band_material" -> List("Rubber"), "dial_color" -> List("Ivory"))
See this fiddle

Try this:
val newFacetFilter =
for ((k,vs) <- facetFilter)
yield (k, vs filter allFacets(k).contains)
Output:
Map(band_material -> List(Rubber), dial_color -> List(Ivory))

OK, if we are done with edits, I think this is what you want...
val allFacets = Map(
"band_material" -> Map(
"Rubber" -> 11
),
"dial_color" -> Map(
"Ivory" -> 68,
"Salmon"-> 3
)
)
val facetFilter = Map(
"band_material" -> List("Yellow Gold Plated", "Rubber"),
"dial_color" -> List("Ivory"),
"case_material" -> List(),
"movement" -> List(),
"price_range" -> List(),
"gender" -> List()
)
val newFacetFilter = for {
(facetName, facetArr) <- facetFilter
(k,v) <- allFacets
if k == facetName
} yield (facetName, facetArr intersect v.keys.toList)
We simply iterate both maps and when we have the same keys, we intersect the two lists.
Edit: There is a more efficient way, using the Map's get function instead of just iterating everything and ignoring non-matches.
val newFacetFilter = facetFilter.flatMap {
case (n, fs) =>
allFacets.get(n).map(n -> _.keys.toList.intersect(fs))
}
So we take each facetFilter entry ((n, fs)), check allFacets for n, then intersect the optional result with our list fs. If n did not exist, we propagate None and it is flattened out by flatMap.

Related

How to merge two maps keeping greater of values for matching keys?

I have two mutable maps in Scala.
val oldMap = Map(10 -> 100, 20 -> 200, 30 -> 300)
val newMap = Map(10 -> 101, 20 -> 200, 30 -> 299, 40 -> 400)
I want to merge newMap into oldMap to get outputMap as below which have all keys from both maps but value is greater of matching key's value.
Map(20 -> 200, 40 -> 400, 10 -> 101, 30 -> 300)
I have tried below and it works but I wanted to know scala way of doing it.
import scala.collection.mutable.Map
object Test extends App {
val oldMap = Map(10 -> 100, 20 -> 200, 30 -> 300)
val newMap = Map(10 -> 101, 20 -> 200, 30 -> 299, 40 -> 400)
val outputMap = mergeMap(oldMap, newMap)
println(outputMap)
def mergeMap(map1: Map[Int, Int], map2: Map[Int, Int]): Map[Int, Int] = {
val map1Keys = map2.keys
val itr = map1Keys.iterator
while (itr.hasNext)
{
val id = itr.next
if (! map1.contains(id)){ // key not present in map1, INSERT
map1(id) = map2(id)
}
else { // key present in map1, UPDATE
if (map2(id) > map1(id)){
map1(id) = map2(id)
}
}
}
map1
}
def commonMapKeys[A, B](a: Map[A, B], b: Map[A, B]): scala.collection.Set[A] = a.keySet.intersect(b.keySet)
}
You can turn maps into collection of tuples, merge them, group by key and select max value in the group:
val oldMap = Map(10 -> 100, 20 -> 200, 30 -> 300)
val newMap = Map(10 -> 101, 20 -> 200, 30 -> 299, 40 -> 400)
(oldMap.toSeq ++ newMap.toSeq)
.groupBy(_._1)
.mapValues(_.map(_._2).max)
Or using groupMapReduce available since Scala 2.13:
(oldMap.toSeq ++ newMap.toSeq)
.groupMapReduce(_._1)(_._2)(math.max(_,_))
newMap.foldLeft(oldMap){ case (result, (k, newV)) =>
val oldV = result.get(k)
result + (k -> oldV.fold(newV)(newV max _))
}
You start from oldMap and iterate through newMap using .foldLeft. In each iteration you take a key and a value from newMap, and try to get the value mapped to the same key from the result map (which is the oldMap at the beginning). You add a new mapping from k to either the new value, or the max of the new and the old values, in case the old map also contained k.
It's pretty much the same thing that you have, just in a more functional way.

toMap on List of List generates error

Given that this example:
val myList = List("age=21", "name=xyz", "profession=Tester", "city=cuba", "age=43", "name=abc", "profession=Programmer", "city=wellington")
val myMap = myList.map(text => text.split("=")).map(a => (a(0) -> a(1))).toMap
works fine, returning:
myList: List[String] = List(age=21, name=xyz, profession=Tester, city=cuba, age=43, name=abc, profession=Programmer, city=wellington)
myMap: scala.collection.immutable.Map[String,String] = Map(age -> 43, name -> abc, profession -> Programmer, city -> wellington)
I am wondering why the following which is just N sets of values:
val myList = List("age=21", "name=xyz", "profession=Tester", "city=cuba", "age=43", "name=abc", "profession=Programmer", "city=Sydney")
val myMap = myList.grouped(4).toList.map(text => text.split("=")).map(a => (a(0) -> a(1))).toMap
generates the error, and how to solve:
notebook:9: error: value split is not a member of List[String]
val myMap = myList.grouped(4).toList.map(text => text.split("=")).map(a => (a(0) -> a(1))).toMap
I must be missing something elementary here.
myList.grouped(4).toList returns a nested list – List[List[String]].
To transform the grouped sublists into Maps:
val myMap = myList.grouped(4).toList.
map(_.map(_.split("=")).map(a => (a(0) -> a(1))).toMap)
// myMap: List[scala.collection.immutable.Map[String,String]] = List(
// Map(age -> 21, name -> xyz, profession -> Tester, city -> cuba),
// Map(age -> 43, name -> abc, profession -> Programmer, city -> Sydney)
// )

Scala - Transform a Iterator into a Map

How to get from an Iterator like this
val it = Iterator("one","two","three","four","five")
a map like
Map(four -> 4, three -> 5, two -> 3, five -> 4, one -> 3)
var m = Map[String, Int]()
while (it.hasNext) {
val cell = it.next()
m += (cell -> cell.length())
}
this is a solution using var but I'd like to use just Immutable and val variable.
If I use the for yield statement the returning object would be a Iterator[Map] and I do not want that:
val m = for(i<- it if it.hasNext) yield Map(i->i.length())
You can just use map:
val m = it.map(c => c -> c.length).toMap

Returning an immutable map containing a single modified entry from another one

I have an immutable map from which I wish to change a single element and then return another immutable map. Am I stuck with copying all elements twice?
val inmap = Map('a'->1,'b'->2)
import collection.mutable
val mmap = mutable.Map(inmap.toList:_*)
mmap('b')= 3
val mmap2 = Map(mmap.toList:_*)
mmap2: scala.collection.immutable.Map[Char,Int] = Map(b -> 3, a -> 1)
Just wondering if that were the 'required' price for immutability on the 'frontiers' of our methods.
You could use the updated method:
scala> val inmap = Map('a' -> 1, 'b' -> 2)
inmap: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 2)
scala> val updated = inmap.updated('b', 3)
updated: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 3)
API doc for Map: look to the updated method.

Convert string with nested delimiters into a map

I want to turn this String:
"1:a|2:b|3:c"
into this:
Map(1 -> "a", 2 -> "b", 3 -> "c")
I have a procedural method that works, but it seems there ought to be a more functional approach.
val a = [YOUR STRING]
a.split('|').map(_.split(':')).map(a => (a(0) -> a(1))).toMap
val s = "1:a|2:b|3:c"
"(\\d+)\\:(\\w+)".r.findAllMatchIn(s).map( m => (m.group(1).toInt -> m.group(2)) ).toMap