Scala Map Reduction and Aggregation - scala

I have a Map that looks like this and is of Type Map[String, Seq[String]]
Map(
"5" -> Seq("5.1"),
"5.1" -> Seq("5.1.1", "5.1.2"),
"5.1.1" -> Seq("5.1.1.1"),
"5.1.2" -> Seq.empty[String],
"5.1.1.1" -> Seq.empty[String]
)
Given a key, I would like to fetch all the values recursively that belongs to the given key. Say for example., if I want to look up for the key 5, I expect the result to be:
Given Input is: 5
Expected Output is: Seq(5.1, 5.1.1, 5.1.2, 5.1.1.1)
Here is what I tried so far:
def fetchSequence(inputId: String, acc: Seq[String], seqMap: Map[String, Seq[String]]): Seq[String] = seqMap.get(inputId) match {
case None => acc
case Some(subSeq) =>
val newAcc = acc ++ subSeq
subSeq.collect {
case subId=> fetchSequence(subId, newAcc, seqMap)
}.flatten
}
I get an empty result when I call fetchSequence with the Map that I have above.

Somewhat more concise :
def recGet[A](map: Map[A, Seq[A]])(key: A): Seq[A] =
map.get(key).fold(
// return empty Seq if key not found
Seq.empty[A])(
// return a Seq with
// the key and
// the result of recGet called recursively
// (for all the elements in the Seq[A] found for key)
x => Seq(key) ++ x.flatMap(recGet(map)))
You can use recGet as :
val sections = Map(
"5" -> Seq("5.1"),
"5.1" -> Seq("5.1.1", "5.1.2"),
"5.1.1" -> Seq("5.1.1.1"),
"5.1.2" -> Seq.empty[String],
"5.1.1.1" -> Seq.empty[String]
)
recGet(sections)("5") // Seq[String] = List(5, 5.1, 5.1.1, 5.1.1.1, 5.1.2)
recGet(sections)("5.1.1") // Seq[String] = List(5.1.1, 5.1.1.1)
recGet(sections)("5.2") // Seq[String] = List()
This will also give you the (first) element itself (if it exists in the map), if you don't want that, you can probably wrap recGet in another method which uses drop(1) on the result of recGet.

Related

How to concatenate two MapViews to get a MapView?

Following code does not compile with Scala 2.13.6:
val a = Map(0 -> "0")
val b = Map(1 -> "1")
val c = a.view ++ b.view
c.contains(0)
The error is:
value contains is not a member of scala.collection.View[(Int, String)]
A similar error is shown in Scala 3 or Scala 2.12.15.
I find this unexpected, as implementation of concat seem to suggest the result should be a map (mapFactory is used to produce the result).
How can I concatenate two MapViews to get a MapView again?
Based on Remove concat, ++ and + overloads from MapView it was removed by design, but perhaps you could still provide your own extension method that mimics previous implementation, something like so
implicit class ConcatMapView[K, +V](left: MapView[K, V]) {
def +++[V1 >: V](right: MapView[K, V1]): MapView[K, V1] =
new AbstractMapView[K, V1] {
def get(key: K): Option[V1] = right.get(key) match {
case s # Some(_) => s
case _ => left.get(key)
}
def iterator: Iterator[(K, V1)] = left.iterator
.filter { case (k, _) => !right.contains(k) }
.concat(right.iterator)
}
}
val c = a.view +++ b.view // : MapView[Int, String] = MapView((0, "0"), (1, "1"))
c.contains(0) // : Boolean = true
I don't know the intent behind concat not returning a MapView but you can achieve your goal like this:
val a = Map(0 -> "0")
val b = Map(1 -> "1")
val c = a.view ++ b.view
val contains = c.exists((k,v) => k == 0)

Scala: How to return the key that contains an item in their value where value is a Set?

I have a Map[Int, Set[Int]]and given an item I want to return the key that indexes to the Set where item exists in. For example if I have a Map(1 -> Set("a"), 2 -> Set("b"), 3 -> Set("c","z")). Say the item is "z" I want to return 3 because 3 is the key that indexes to the set that contains 3.
Here is what I currently have, but I can't seem to find a good way to get the key. I can only get the value, the Set[Int]. Assume the item will only be in one possible set.
def find(djs: Map[Int, Set[Int]], item: Int) :Int = {
for ((key, set) <- djs) {
if (set.contains(item)) {
key
}
}
collectFirst is made for this:
val m = Map(1 -> Set("a"), 2 -> Set("b"), 3 -> Set("c","z"))
m.collectFirst{case (k,v) if v.contains("z") => k}
//> res0: Option[Int] = Some(3)
and I always forget Set can be used as a function itself (i.e. apply is the same as contains)
m.collectFirst{case (k,v) if v("z") => k}
//> res0: Option[Int] = Some(3)
If you need to return one or None then Option[] would be the way to go. If returning one, many, or none is required then returning a List[] might be in order.
val m = Map(1 -> Set("a"), 2 -> Set("b"), 3 -> Set("c","z"))
m.flatMap{ case (k,vs) => if (vs.contains("z")) Seq(k) else Seq() } // List(3)
m.flatMap{ case (k,vs) => if (vs.contains("w")) Seq(k) else Seq() } // List()
The find operator can work well here:
def find[A, B](m: Map[A, Set[B]], item: B): Option[A] =
m.find { case (key, set) => set.contains(item) }
.map { case (key, set) => key }

applying partial function on a tuple field, maintaining the tuple structure

I have a PartialFunction[String,String] and a Map[String,String].
I want to apply the partial functions on the map values and collect the entries for which it was applicaple.
i.e. given:
val m = Map( "a"->"1", "b"->"2" )
val pf : PartialFunction[String,String] = {
case "1" => "11"
}
I'd like to somehow combine _._2 with pfand be able to do this:
val composedPf : PartialFunction[(String,String),(String,String)] = /*someMagicalOperator(_._2,pf)*/
val collected : Map[String,String] = m.collect( composedPf )
// collected should be Map( "a"->"11" )
so far the best I got was this:
val composedPf = new PartialFunction[(String,String),(String,String)]{
override def isDefinedAt(x: (String, String)): Boolean = pf.isDefinedAt(x._2)
override def apply(v1: (String, String)): (String,String) = v1._1 -> pf(v1._2)
}
is there a better way?
Here is the magical operator:
val composedPf: PartialFunction[(String, String), (String, String)] =
{case (k, v) if pf.isDefinedAt(v) => (k, pf(v))}
Another option, without creating a composed function, is this:
m.filter(e => pf.isDefinedAt(e._2)).mapValues(pf)
There is a function in Scalaz, that does exactly that: second
scala> m collect pf.second
res0: scala.collection.immutable.Map[String,String] = Map(a -> 11)
This works, because PartialFunction is an instance of Arrow (a generalized function) typeclass, and second is one of the common operations defined for arrows.

Scala: Count Words

I am trying to write the function of countWords(ws) that counts the frequency of words in a list of words ws returning a map from words to occurrences.
that ws is a List[String], using the List data type I should produce a Map[String,Int] using the Map data type. an example of what should the function do:
def test{
expect (Map("aa" -> 2, "bb" -> 1)) {
countWords(List("aa", "bb"))
}
}
This is just a perpetration for a test and its not an assignment. I have been stuck on this function for while now. This is what I have so far:
object Solution {
// define function countWords
def countWords(ws : List[String]) : Map[String,Int] = ws match {
case List() => List()
}
}//
which gives type mismatch. I am not quite sure how to use the scala Map Function, for example when ws is Empty list what should it return that passed by Map[String,Int] I have been trying, and thats why I post it here to get some help. thank you.
Another way to do it is using groupBy which outputs Map(baz -> List(baz, baz, baz), foo -> List(foo, foo), bar -> List(bar)). Then you can map the values of the Map with mapValues to get a count of the number of times each word appears.
scala> List("foo", "foo", "bar", "baz", "baz", "baz")
res0: List[String] = List(foo, foo, bar, baz, baz, baz)
scala> res0.groupBy(x => x).mapValues(_.size)
res0: scala.collection.immutable.Map[String,Int] = Map(baz -> 3, foo -> 2, bar -> 1)
Regarding the type mismatch in your program countWords is expecting a Map[String, Int] as the return type and the first(and only) match you have returns an empty List with type Nothing. If you change the match to case List() => Map[String, Int]() it will no longer give a type error. It also gives a warning about an in-exhaustive pattern match obviously won't return the correct output.
The easiest solution is this
def countWords(ws: List[String]): Map[String, Int] = {
ws.toSet.map((word: String) => (word, ws.count(_ == word))).toMap
}
But it's not the fastest one since it searches through the list several times.
edit:
The fastest way is to use a mutable HashMap
def countWords(ws: List[String]): Map[String, Int] = {
val map = scala.collection.mutable.HashMap.empty[String, Int]
for(word <- ws) {
val n = map.getOrElse(word, 0)
map += (word -> (n + 1))
}
map.toMap
}
Use fold to go through your list starting with an empty map
ws.foldLeft(Map.empty[String, Int]){
(count, word) => count + (word -> (count.getOrElse(word, 0) + 1))
}

Search for items in a Map

I am new to scala, my objective is to iterate over list and check if items in the list exist the map as its keys and if they exist return the value for that key.
I did the following:
def getMatchingValues(listItmes: List[String]) = {
for (item <- listItems) {
theMap.keys.foreach { i =>
if (i.equals(item)) {
theMap(i)
}
"NoMatch"
}
}
}
I am trying to figure if there is a better way to do this in scala?
Map has a getOrElse method which does what you want:
def getMatchingValues(listItems: List[String]) = listItems map (theMap.getOrElse(_,"NoMatch"))
At least I think that is what you want. Here's an example:
scala> val theMap = Map("a"->"A", "b" -> "B")
theMap: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B)
scala> val listItems = List("a","b","c")
listItems: List[String] = List(a, b, c)
scala> listItems map (theMap.getOrElse(_,"NoMatch"))
res0: List[String] = List(A, B, NoMatch)
A possible solution with flatMap:
/* Return Some[String] if found, None if not found, then flatten */
def getMatchingValues(listItems: List[String], theMap: Map[String, String]): List[String] =
listItems.flatMap(item => theMap.get(item))
/* Same thing with some syntactic sugar */
def getMatchingValuesSmartass(listItems: List[String], theMap: Map[String, String]): List[String] =
listItems flatMap theMap.get
val l = List("1", "3", "5", "7")
val m = Map("5" -> "five", "2" -> "two", "1" -> "one")
getMatchingValues(l, m)
getMatchingValuesSmartass(l, m)
You could use the map.get method and handle the result with pattern matching
list.map { x => map.get(x) match {
case None => "No match"
case Some(i) => (x, i)
}}
The above code returns a list of pairs where each pair represents the elements of the list and the value associated in the map ("No match" if not found)
If I was you, I would do two steps. Given this Map:
val map = Map("a" -> "b", "b" -> "c", "c" -> "d", "d" -> "e")
and this List:
val list = List("a", "c", "e")
At first I would map an Option value to every item in the List. Giving you if there is a value for the item.
val mapped = list.map(item => item -> map.get(item))
This will give you this:
mapped: List[(String, Option[String])] =
List(("a",Some("b")), ("c",Some("d")), ("e", None))
Calling get on the map returns a wrapped result. If there is a result, you will get the result wrapped in a Some. Otherwise you will get a None. Both are subclasses of Option Option is a closure-construct, that provides you a null-value without having to deal with null. Now you are able to map again, to reach your goal.
val result = mapped.map(tuple => tuple._1 -> tuple._2.getOrElse("No match"))
result: List[(String, String)] = List(("a","b"), ("c","d"), ("e","No match"))
getOrElse extracts the value of a Some or falls back to the parameter if it is a None.
To make it look more professional, we can write this expression in one line ;)
val result = list.map(item => item -> map.get(item).getOrElse("No match"))
This will give you the exact same result.