Appending to Map with value as a list - scala

I have initialized a mutable Map (not sure if I should use a mutable here, but to start with I use mutable):
val aKey = "aa"
val myMap = scala.collection.mutable.Map[String, List[String]]()
if (myMap.exists(_._1 == aKey))
myMap(aKey) = myMap.get(aKey) :: "test"
But on myMap.get(aKey) I get the following error:
Type Mismatch expected List[String] actual option[String]
I thought the way I did is correct to append to list.

You can append to a mutable map with +=.
scala> myMap += ("placeholders" -> List("foo", "bar", "baz"))
res0: scala.collection.mutable.Map[String,List[String]] = Map(placeholders -> List(foo, bar, baz))
To append a new item to the list for aKey as mentioned in the commments.
myMap.get("placeholders") match {
case Some(xs:List[String]) => myMap.update("placeholders", xs :+ "buzz")
case None => myMap
}
res22: Any = ()
scala> myMap
res23: scala.collection.mutable.Map[String,List[String]] = Map(placeholders -> List(foo, bar, baz, buzz))

If you have mutable Map and inside that map immutable List. This is a simple example on how to do it. The example is also defining using withDefaultValue - so that you always get something back from Map.
var posts: collection.mutable.Map[Int, List[String]] = collection.mutable.Map().
withDefaultValue List.empty[String]
def addTag(postID: Int, tag: String): Unit = posts.get(postID) match {
case Some(xs: List[String]) => posts(postID) = xs :+ tag
case None => posts(postID) = List(tag)
}
posts(42)
// List[String] = List()
addTag(42, "tag-a")
addTag(42, "tag-b")
addTag(42, "tag-c")
posts(42)
// List[String] = List(tag-a, tag-b, tag-c)

Everything is fine. Except for list append operator
To add an element to the list. The operator should be something like
myList = element :: myList
myList = myList :: element // wrong
so your program should be
val aKey = "aa"
var myMap = scala.collection.mutable.Map[String, List[String]]().withDefaultValues(List())
myMap(aKey) = "test" :: myMap(aKey)

Starting Scala 2.13, you could alternatively use Map#updateWith on mutable Maps (or Map#updatedWith on immutable Maps):
map.updateWith("a")({
case Some(list) => Some("test" :: list)
case None => Some(List("test"))
})
def updateWith(key: K)(remappingFunction: (Option[V]) => Option[V]): Option[V]
For instance,
val map = collection.mutable.Map[String, List[String]]()
// map: collection.mutable.Map[String, List[String]] = HashMap()
val key = "key"
// key: String = "key"
if the key doesn't exist:
map.updateWith(key)({ case Some(list) => Some("test" :: list) case None => Some(List("test")) })
// Option[List[String]] = Some(List("test"))
map
// collection.mutable.Map[String, List[String]] = HashMap("key" -> List("test"))
and if the key exists:
map.updateWith(key)({ case Some(list) => Some("test2" :: list) case None => Some(List("test2")) })
// Option[List[String]] = Some(List("test2", "test"))
map
// collection.mutable.Map[String, List[String]] = HashMap("key" -> List("test2", "test"))

I figured the how to do it:
val aKey = "aa"
var myMap = scala.collection.mutable.Map[String, List[String]]()
if (myMap.exists(_._1 == aKey))
myMap.get(aKey).get :+ "foo"
else {
myMap(aKey) = List("zoo")
}
This might not be the scala way of doing but this gives the correct results.

first you shouldn't Mutable Map :). for add one item on List within Map, you can use method get of Map.
val m = Map(1 -> List(2))
val m2 = m.get(1).fold{
// In this case don't exist any List for this key
m + (1 -> List(3))
}{ list =>
// In this case exist any List for this key
m + (1 -> (3 :: list))
}

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)

Trying to return a Map but an Iterable is currently being returned

Why is my val pairOpt a Option[Option[String,String]]?
I am trying to have it so it returns Option[(String, String)].
def blah(..): Map[String, String] = {
val map: Map[String, String] = //
val boolTry = Try(map.getOrElse("key1", "").trim.toBoolean)
val intTry = Try(map.getOrElse("key2, "").trim.toInt)
val pairOpt: Option[Option[(String, String)]] = for {
b <- boolTry.toOption
i <- intTry.toOption
} yield {
val res: Option[String] = (b, i) match {
case (true, 1) => Some("a")
case (false, 2 | 3 | 7) => Some("b")
case (true, 5 | 9 | 11) => Some("c")
case _ => None
}
res.map("foo" -> _)
}
map ++ pairOpt // map + ("foo" -> "c")
}
The return value is also currenly Iterable[Product with Serializable] when I want it to be Map[String,String].
What am I missing here?
You get an Option[Option[..]] because you have two "layers" of "optionality" here that you have to flatten:
If one of boolTry or intTry is a Failure - you'll get None
Else, if they're both Success but their values don't match anything, you'll get Some(None)
Otherwise, you'll get Some(Some(..))
More generally, given a opt: Option[V], the type of an expression of the form:
for {
x <- opt
..
} yield {
val y: T
y
}
is Option[T] - because it translates into opt.flatMap(...).map(...) which preserves the "external" structure (be it an Option, List, Seq etc.). In your case, T = Option[(String, String)], so the result has the type Option[Option[(String, String)]]. To fix this - you can use flatten:
val pairOpt: Option[(String, String)] = (for {
b <- boolTry.toOption
i <- intTry.toOption
} yield {
// same as you did...
}).flatten
Which would also fix the issue with the method's return type (now map ++ pairOpt will have the type Map[String, String] as expected).
To avoid this call to flatten - perhaps a cleaner way to achieve the same would be:
val maybeTuple: Option[(Boolean, Int)] = boolTry.flatMap(b => intTry.map((b, _))).toOption
val pairOpt: Option[(String, String)] = maybeTuple.flatMap {
case (true, 1) => Some("a")
case (false, 2 | 3 | 7) => Some("b")
case (true, 5 | 9 | 11) => Some("c")
case _ => None
}.map("foo" -> _)

wrapping different datatype into a generic, in scala

I expect to return a map containing value of different datatypes such as
(key -> String) and (key -> Int), but i can have Map either of
Map[String,String] or Map[String,Int].
I can't use class because number and order of keys are not fixed.
Is there any way to wrap String and Int to a generic class so that i can return map as Map[String,Any]
You can use HMap as #Harnish suggested, but there is an alternative in the scala library: Map[String, Either[Int, String]]. It applies only if you know that the types either one or another and nothing more.
The type Either[Int, String] can be created either by Left(5) or Right("Hello"). Then you can use match to test the value:
x match {
case Left(n) => println(s"$n is a number")
case Right(s) => println(s"$s is a string")
}
Updated
Example:
val dict = scala.collection.mutable.Map[String, Either[String, Int]]()
dict += ("a" -> Right(5))
dict += ("b" -> Left("Hello"))
dict map {
case (key, Right(n)) => println(s"For $key: $n is integer")
case (key, Left(s)) => println(s"For $key: $s is string")
}
I'm not sure if you can do this with the standard collections library, however it is possible using shapeless HMap (Heterogenous map). This is the example given in the docs, which closely matches what you have described:
// Key/value relation to be enforced: Strings map to Ints and vice versa
class BiMapIS[K, V]
implicit val intToString = new BiMapIS[Int, String]
implicit val stringToInt = new BiMapIS[String, Int]
val hm = HMap[BiMapIS](23 -> "foo", "bar" -> 13)
//val hm2 = HMap[BiMapIS](23 -> "foo", 23 -> 13) // Does not compile
scala> hm.get(23)
res0: Option[String] = Some(foo)
scala> hm.get("bar")
res1: Option[Int] = Some(13)
Note, it doesn't give you an Any, instead you have to specify what is valid in your key/value pairs. I'm not sure if that's helpful to you or not...

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.

How do I parse a x-www-url-encoded string into a Map[String, String] using Lift?

From Lift, I'm getting a string of the form
TOKEN=EC%2d454178600K772032D&TIMESTAMP=2011%2d06%2d29T13%3a10%3a58Z&CORRELATIONID=cbd56e97cad38&ACK=Success&VERSION=64&BUILD=1936884
from the response of an HTTP request.
Although it's probably ultra-trivial, I can't find the Lift function that parses this into a nice Map[String, String]. Any help?
From Lift's Req.scala:
// calculate the query parameters
lazy val queryStringParam: (List[String], Map[String, List[String]]) = {
val params: List[(String, String)] =
for {
queryString <- request.queryString.toList
nameVal <- queryString.split("&").toList.map(_.trim).filter(_.length > 0)
(name, value) <- nameVal.split("=").toList match {
case Nil => Empty
case n :: v :: _ => Full((urlDecode(n), urlDecode(v)))
case n :: _ => Full((urlDecode(n), ""))
}} yield (name, value)
val names: List[String] = params.map(_._1).distinct
val nvp: Map[String, List[String]] = params.foldLeft(Map[String, List[String]]()) {
case (map, (name, value)) => map + (name -> (map.getOrElse(name, Nil) ::: List(value)))
}
(names, nvp)
}
I haven't seen any Lift's implementation for that. You can achieve this with something like this:
val input = "TOKEN=EC%2d454178600K772032D&TIMESTAMP=2011%2d06%2d29T13%3a10%3a58Z&CORRELATIONID=cbd56e97cad38&ACK=Success&VERSION=64&BUILD=1936884"
val res = input.split('&') map { str =>
val pair = str.split('=')
(pair(0) -> pair(1))
} toMap
note: it assumes, that you have a well-formed string. In your code you should probably check if the string is ok.
I put together a small Scala library to help do this: https://github.com/theon/scala-uri
You can parse a uri and get the parameters into a Map[String,List[String]] like so:
val uri = parseUri("http://example.com?one=1&two=2").query.params
It also has a DSL for building URLs with query strings:
val uri = "http://example.com" ? ("one" -> 1) & ("two" -> 2)
scala> val queryParams = "a=4&b=5"
scala> queryParams.split("&").toList.map(_.trim).filter(_.length > 0).flatMap(x => {
val s = x.split('=')
Map[String, String](s(0) -> s(1))
}).toMap[String, String]
res0: scala.collection.immutable.Map[String,String] = Map(a -> 4, b -> 5)