Scala Map conversion - scala

I'm a Scala newbie I'm afraid:
I'm trying to convert a Map to a new Map based on some simple logic:
val postVals = Map("test" -> "testing1", "test2" -> "testing2", "test3" -> "testing3")
I want to test for value "testing1" and change the value (while creating a new Map)
def modMap(postVals: Map[String, String]): Map[String, String] = {
postVals foreach {case(k, v) => if(v=="testing1") postVals.update(k, "new value")}
}

You could use the 'map' method. That returns a new collection by applying the given function to all elements of it:
scala> def modMap(postVals: Map[String, String]): Map[String, String] = {
postVals map {case(k, v) => if(v == "a") (k -> "other value") else (k ->v)}
}
scala> val m = Map[String, String]("1" -> "a", "2" -> "b")
m: scala.collection.immutable.Map[String,String] = Map((1,a), (2,b))
scala> modMap(m)
res1: Map[String,String] = Map((1,other value), (2,b))

Alternative to Arjan's answer: (just a slight change)
scala> val someMap = Map("a" -> "apple", "b" -> "banana")
someMap: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(a -> apple, b -> banana)
scala> val newMap = someMap map {
| case(k , v # "apple") => (k, "alligator")
| case pair => pair
| }
newMap: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(a -> alligator, b -> banana)

even easier:
val myMap = Map("a" -> "apple", "b" -> "banana")
myMap map {
case (k, "apple") => (k, "apfel")
case pair => pair
}

Related

Scala: How to sum the mapValues in Map[String, List[(String, Map[Long, Int])]]

It is a complicated structure for example:
val testMap: Map[String, List[(String, Map[Long, Int])]] = Map(
"test1" ->
List(
("test1", Map(1111111111L -> 2)),
("test1", Map(1111111111L -> 2)),
("test1", Map(1111111111L -> 2)),
("test1", Map(1111111111L -> 2)),
("test1", Map(2222222222L -> 2))
)
)
How can I sum the values with the same key? I'm expecting the result to be:
Map(test1 -> Map(1111111111 -> 8, 2222222222 -> 2))
What I've tried so far is:
val res = testMap.mapValues(_.map(_._2).reduce(_ ++ _))
BUT the result I get is:
Map(test1 -> Map(1111111111 -> 2, 2222222222 -> 2))
1111111111 has the value 2 instead of 8. How can I fix this? Thanks!
If you need reduce your values in your inner map you can use foldLeft and accumulate result map:
def combineInner(mapA: Map[Long, Int], mapB: Map[Long, Int]): Map[Long, Int] = {
mapA.foldLeft(mapB) {
case (mapWithSum, (key, value)) =>
mapWithSum.updated(key, mapWithSum.getOrElse(key, 0) + value)
}
}
val res = testMap.mapValues(_.map(_._2).reduce(combineInner))
but remember you will lost outer map keys in this way and string values in list (thirst element in list of pairs).
UPDATE:
If you can use cats library, you can do more simple just using cats semigroup type class:
import cats.implicits._
val res = testMap.mapValues(_.map(_._2).reduce(_ |+| _))
you could try something like this I know it looks little complicated but I was trying to cover some corner case to make the code more dynamic
testMap.mapValues{values =>
values.groupBy(_._1).flatMap{
case (_ , values) =>
values.foldLeft(Map.empty[Long, Int]){
case (acc, (_, nextMap)) =>
nextMap.flatMap{
case (key, value) =>
acc.get(key).fold(acc + (key -> value))(intValue => acc + (key -> (value + intValue)))
}
}
}
}
Extending on your own solution:
val testMap: Map[String, List[(String, Map[Long, Int])]] = Map(
"test1" ->
List(
("test1", Map(1111111111L -> 2)),
("test1", Map(1111111111L -> 2)),
("test1", Map(1111111111L -> 2)),
("test1", Map(1111111111L -> 2)),
("test1", Map(2222222222L -> 2))
)
)
val res = testMap.map(t => (t._1, t._2.map(_._2).reduce((map1, map2) => {
val combinedMap = map1.map {
case (k, v1) =>
(k, map2.get(k).map(v2 => v1 + v2).getOrElse(v1))
}
val uniqueKeyFromMap2 = map2.filterNot(t => map1.contains(t._1))
combinedMap ++ uniqueKeyFromMap2
})))

Scala groupBy for a list

I'd like to create a map on which the key is the string and the value is the number of how many times the string appears on the list. I tried the groupBy method, but have been unsuccessful with that.
Required Answer
scala> val l = List("abc","abc","cbe","cab")
l: List[String] = List(abc, abc, cbe, cab)
scala> l.groupBy(identity).mapValues(_.size)
res91: scala.collection.immutable.Map[String,Int] = Map(cab -> 1, abc -> 2, cbe -> 1)
Suppose you have a list as
scala> val list = List("abc", "abc", "bc", "b", "abc")
list: List[String] = List(abc, abc, bc, b, abc)
You can write a function
scala> def generateMap(list: List[String], map:Map[String, Int]) : Map[String, Int] = list match {
| case x :: y => if(map.keySet.contains(x)) generateMap(y, map ++ Map(x -> (map(x)+1))) else generateMap(y, map ++ Map(x -> 1))
| case Nil => map
| }
generateMap: (list: List[String], map: Map[String,Int])Map[String,Int]
Then call the function as
scala> generateMap(list, Map.empty)
res1: Map[String,Int] = Map(abc -> 3, bc -> 1, b -> 1)
This also works:
scala> val l = List("abc","abc","cbe","cab")
val l: List[String] = List(abc, abc, cbe, cab)
scala> l.groupBy(identity).map(x => (x._1, x._2.length))
val res1: Map[String, Int] = HashMap(cbe -> 1, abc -> 2, cab -> 1)

Using filterNot with a Map

I have a simple map
val m = Map("a1" -> "1", "b2" -> "2", "c3" -> "3")
val someList = List("b", "d")
m.filterNot( (k,v) => someList.exist(l => k.startsWith(l)) )
I'm getting an error:
error: missing parameter type
I'm doing something silly here I'm sure, why isn' this compiling?
filterNot needs a case keyword and {} when you extract k, v from tuple.
note that its not exist its exists
m.filterNot { case (k,v) => someList.exists(l => k.startsWith(l)) }
or
m.filterNot(pair => someList.exists(l => pair._1.startsWith(l)))
Explanation
As you are extracting k, v from the tuple using extractor syntax you have to use case keyword and {}
Without extractor syntax you can do
m.filterNot(pair => someList.exists(l => pair._1.startsWith(l)))
Scala REPL
scala> val m = Map("a1" -> "1", "b2" -> "2", "c3" -> "3")
m: scala.collection.immutable.Map[String,String] = Map(a1 -> 1, b2 -> 2, c3 -> 3)
scala> val someList = List("b", "d")
someList: List[String] = List(b, d)
scala> m.filterNot { case (k,v) => someList.exists(l => k.startsWith(l)) }
res15: scala.collection.immutable.Map[String,String] = Map(a1 -> 1, c3 -> 3)
Without extractor syntax
Now you need not use case keyword and {} as we are not using extracting the key and value using extractor syntax
scala> m.filterNot(pair => someList.exists(l => pair._1.startsWith(l)))
res18: scala.collection.immutable.Map[String,String] = Map(a1 -> 1, c3 -> 3)
scala> val m = Map("a1" -> "1", "b2" -> "2", "c3" -> "3")
m: scala.collection.immutable.Map[String,String] = Map(a1 -> 1, b2 -> 2, c3 -> 3)
scala> val someList = List("b", "d")
someList: List[String] = List(b, d)
scala> m.filterNot(pair => someList.exists(l => pair._1.startsWith(l)))
res19: scala.collection.immutable.Map[String,String] = Map(a1 -> 1, c3 -> 3)
The issue is this: the filterNot method accepts one parameter, while you are defining a list of two parameters. This confuses the compiler and that message is the result.
In order to solve it, you can use the following syntax (notice the usage of pattern matching with the case keyword):
m.filterNot { case (k,v) => someList.exist(l => k.startsWith(l)) }
Using the pattern matching like this creates a PartialFunction that will be decompose key and value and be applied like a normal function to your Map.
Using for comprehension syntax, extraction and filtering may be achieved as follows,
for ( pair#(k,v) <- m; l <- someList if !k.startsWith(l)) yield pair

How to tell if a Map has a default value?

Is there a way to check if a Map has a defined default value? What I would like is some equivalent of myMap.getOrElse(x, y) where if the key x is not in the map,
if myMap has a default value, return that value
else return y
A contrived example of the issue:
scala> def f(m: Map[String, String]) = m.getOrElse("hello", "world")
f: (m: Map[String,String])String
scala> val myMap = Map("a" -> "A").withDefaultValue("Z")
myMap: scala.collection.immutable.Map[String,String] = Map(a -> A)
scala> f(myMap)
res0: String = world
In this case, I want res0 to be "Z" instead of "world", because myMap was defined with that as a default value. But getOrElse doesn't work that way.
I could use m.apply instead of m.getOrElse, but the map is not guaranteed to have a default value, so it could throw an exception (I could catch the exception, but this is nonideal).
scala> def f(m: Map[String, String]) = try {
| m("hello")
| } catch {
| case e: java.util.NoSuchElementException => "world"
| }
f: (m: Map[String,String])String
scala> val myMap = Map("a" -> "A").withDefaultValue("Z")
myMap: scala.collection.immutable.Map[String,String] = Map(a -> A)
scala> f(myMap)
res0: String = Z
scala> val mapWithNoDefault = Map("a" -> "A")
mapWithNoDefault: scala.collection.immutable.Map[String,String] = Map(a -> A)
scala> f(mapWithNoDefault)
res1: String = world
The above yields the expected value but seems messy. I can't pattern match and call apply or getOrElse based on whether or not the map had a default value, because the type is the same (scala.collection.immutable.Map[String,String]) regardless of default-ness.
Is there a way to do this that doesn't involve catching exceptions?
You can check whether the map is an instance of Map.WithDefault:
implicit class EnrichedMap[K, V](m: Map[K, V]) {
def getOrDefaultOrElse(k: K, v: => V) =
if (m.isInstanceOf[Map.WithDefault[K, V]]) m(k) else m.getOrElse(k, v)
}
And then:
scala> val myMap = Map("a" -> "A").withDefaultValue("Z")
myMap: scala.collection.immutable.Map[String,String] = Map(a -> A)
scala> myMap.getOrDefaultOrElse("hello", "world")
res11: String = Z
scala> val myDefaultlessMap = Map("a" -> "A")
myDefaultlessMap: scala.collection.immutable.Map[String,String] = Map(a -> A)
scala> myDefaultlessMap.getOrDefaultOrElse("hello", "world")
res12: String = world
Whether this kind of reflection is any better than using exceptions for non-exceptional control flow is an open question.
You could use Try instead of try/catch, and it would look a little cleaner.
val m = Map(1 -> 2, 3 -> 4)
import scala.util.Try
Try(m(10)).getOrElse(0)
res0: Int = 0
val m = Map(1 -> 2, 3 -> 4).withDefaultValue(100)
Try(m(10)).getOrElse(0)
res1: Int = 100

SortedMap of SortedMaps from Map of List of objects, keyed on object member

I have an unordered map:
class O(val a: Int)
Map[String, List[O]]
which I'd like to turn into:
SortedMap[String, SortedMap[Int, O]]
with the child SortedMap keyed on the O field.
I'm sure there must be a more idiomatic code than the below...
class O(val a: Int)
val a: Map[String, List[O]] = Map[String, List[O]]( ("b" -> List(new O(3), new O(2))), "a" -> List(new O(1), new O(2)))
val key1s = a map (_._1)
val oMapsList = ListBuffer[SortedMap[Int, O]]()
for (key1 <- key1s) {
val oList = a(key1)
val key2s = oList map (_.a)
val sortedOMap = SortedMap[Int, O]() ++ (key2s zip oList).toMap
oMapsList += sortedOMap
}
val sortedMap = SortedMap[String, SortedMap[Int, O]]() ++ (key1s zip oMapsList).toMap
Expected sortedMap contents is:
"a" -> ( (1 -> O(1)),(2 -> O(2)) )
"b" -> ( (2 -> O(2)),(2 -> O(3)) )
Firstly, the setup:
scala> case class O(i: Int)
defined class O
scala> Map("a" -> List(O(1), O(2)), "b" -> List(O(2), O(3)))
res0: scala.collection.immutable.Map[java.lang.String,List[O]] = Map(a -> List(O(1), O(2)), b -> List(O(2), O(3)))
Now, import SortedMap:
scala> import collection.immutable._
import collection.immutable._
Now for the answers!
Using breakOut (1 line of code)
Use breakOut - but it involves some unwelcome repetition of types:
scala> res0.map({ case (s, l) => s -> (l.map(o => o.i -> o)(collection.breakOut): SortedMap[Int, O]) })(collection.breakOut): SortedMap[String, SortedMap[Int, O]]
res4: scala.collection.immutable.SortedMap[String,scala.collection.immutable.SortedMap[Int,O]] = Map(a -> Map(1 -> O(1), 2 -> O(2)), b -> Map(2 -> O(2), 3 -> O(3)))
Using a separate method (2 lines of code)
Or a second approach would be to involve a sort method:
scala> def sort[K: Ordering, V](m: Traversable[(K, V)]) = SortedMap(m.toSeq: _ *)
sort: [K, V](m: scala.collection.immutable.Traversable[(K, V)])(implicit evidence$1: Ordering[K])scala.collection.immutable.SortedMap[K,V]
And so:
scala> sort(res0.mapValues(l => sort(l.map(o => o.i -> o)) ))
res13: scala.collection.immutable.SortedMap[java.lang.String,scala.collection.immutable.SortedMap[Int,O]] = Map(a -> Map(1 -> O(1), 2 -> O(2)), b -> Map(2 -> O(2), 3 -> O(3)))