update keys in scala - scala

I have to update keys in a map based on certain condition. values will not be modified.
I have ended up with this code, but doesn't look neat to me, is there any better alternative of doing this:
val newOpt = inOpt("type") match {
case "mydb" =>
inOpt map { case (key, value) => (
if (key.contains(XXXX)) {
key.replace("A", "B")
}
else if(...){..}
else {
key
}
, value)
}
}
All the updated keys along with old keys and values will be in newOpt.
Regards

try this
val m = Map(1->"hi",2->"Bye")
scala.collection.immutable.Map[Int,String] = Map(1 -> hi, 2 -> Bye)
Update the key 2 with 5
m.map{x=> if(x._1 == 2) (5 -> x._2) else x}
scala.collection.immutable.Map[Int,String] = Map(1 -> hi, 5 -> Bye)

At least you can separate the updateKey logic to a different function, also you might use pattern matching for if else.
val newMap = originalMap map { case (key, value) => (updatedKey(key), value)}
At least it might look cleaner but not necessarily better solution than yours.
class UpdateKeysSpecs extends FunSuite {
test("updates the keys") {
val originalMap : Map[String, String]= Map("key1" -> "value1",
"key2_old" -> "value2",
"key3_something" -> "value3")
val newMap = originalMap map { case (key, value) => (updatedKey(key), value)}
assert(newMap.keys == Set("key1", "key2_new", "key3_nothing"))
}
def updatedKey(key: String): String = {
key.contains("old") match {
case true => key.replace("old", "new")
case false => {
key.contains("something") match {
case true => key.replace("something", "nothing")
case false => key
}
}
}
}
}

For
val m = Seq((1,2),(11,22)).toMap
m: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 11 -> 22)
create a new map based in m with updated,
val m2 = m.updated(11,23)
m2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 11 -> 23)

Related

How to find duplicate values in Map

I have the following Map[String, Int]:
val m = Map[String, Int](
"[LOGIN-011]" -> 0,
"[LOGIN-103]" -> 3,
"[LOGIN-222]" -> 10,
"[ERROR-110]" -> 1,
"[ERROR-012]" -> 3,
...
)
How to find duplicated values in the Map and print the values with List[String] as follows:
3 -> List("[LOGIN-103]", "[ERROR-012]")
Try
m
.toSeq
.groupBy { case (key, value) => value }
.collect { case (key, values: List[(String, Int)]) if values.size > 1 => (key, values.map(_._1)) }
which outputs
HashMap(3 -> List([ERROR-012], [LOGIN-103]))
Here is Luis' one-liner:
m.groupBy(_._2).collect { case (key, group: Map[String, Int]) if group.size > 1 => (key, group.keySet) }
Following works in scala 2.13+ only
val map = Map (
"[LOGIN-011]" -> 0,
"[LOGIN-103]" -> 3,
"[LOGIN-222]" -> 10,
"[ERROR-110]" -> 1,
"[ERROR-012]" -> 3
)
val duplicateValues = map.groupMap(_._2)(_._1).filterNot(_._2.sizeIs == 1)
//Map(3 -> List([ERROR-012], [LOGIN-103]))

How to get the closest value from map by given key in scala?

For example, I have a Map[Integer,String] like
val map = Map(1 -> "a", 2 -> "b", 3 -> "c", 5 -> "d", 9 -> "e", 100 -> "z")
If given key is 2, then "b" is expected to return.
If given key is 50, then "e" and "z" are expected to return.
If given key is 0, then "a" is expected to return.
In other words, if the key exists in the Map the corresponding value should be returned. Otherwise the values of the closest smaller and larger keys should be returned (in the case no other key is smaller only the value of the closest larger key should be returned and vice versa).
How can this be accomplished?
Map doesn't preserve order hence I would suggest creating a method that:
converts the Map into a TreeMap
generates the lower/upper Map entries as Options in a list using to(key).lastOption and from(key).headOption, respectively
flattens the list and extracts the Map values:
Sample code as follows:
val map = Map(1->"a", 2->"b", 100->"z", 9->"e", 3->"c", 5->"d")
def closestValues(m: Map[Int, String], key: Int): Seq[String] = {
import scala.collection.immutable.TreeMap
val tm = TreeMap(m.toSeq: _*)
Seq( tm.to(key).lastOption, tm.from(key).headOption ).
flatten.distinct.map{ case (k, v) => v }
}
closestValues(map, 0)
// res1: Seq[String] = List(a)
closestValues(map, 2)
// res2: Seq[String] = List(b)
closestValues(map, 50)
// res3: Seq[String] = List(e, z)
closestValues(map, 101)
// res4: Seq[String] = List(z)
UPDATE:
Starting Scala 2.13, methods to and from for TreeMap are replaced with rangeTo and rangeFrom, respectively.
My 2-cents worth.
def getClose[K](m: Map[Int,K], k: Int): Seq[K] =
if (m.get(k).nonEmpty) Seq(m(k))
else {
val (below,above) = m.keys.partition(_ < k)
Seq( if (below.isEmpty) None else Some(below.max)
, if (above.isEmpty) None else Some(above.min)
).flatten.map(m)
}
I would recommend first converting the Map to a SortedMap since the order of the keys needs to be taken into account.
val map = Map(1->"a",2->"b",3->"c",5->"d",9->"e",100->"z")
val sortedMap = SortedMap[Int, String]() ++ map
After that, use the following method to get the closest values. The result is returned as a List.
def getClosestValue(num: Int) = {
if (sortedMap.contains(num)) {
List(sortedMap(num))
} else {
lazy val larger = sortedMap.filterKeys(_ > num)
lazy val lower = sortedMap.filterKeys(_ < num)
if (larger.isEmpty) {
List(sortedMap.last._2)
} else if (lower.isEmpty) {
List(sortedMap.head._2)
} else {
List(lower.last._2, larger.head._2)
}
}
}
Testing it with the following values:
println(getClosestValue(2))
println(getClosestValue(50))
println(getClosestValue(0))
println(getClosestValue(101))
will give
List(b)
List(z, e)
List(a)
List(z)
This not an efficient solution but you can do something like below
val map =Map(1->"a",2->"b",3->"c",5->"d",9->"e",100->"z")
val keyset = map.keySet
def getNearestValues(key: Int) : Array[String] = {
if(keyset.contains(key)) Array(map(key))
else{
var array = Array.empty[String]
val less = keyset.filter(_ < key)
if(!less.isEmpty) array = array ++ Array(map(less.toList.sortWith(_ < _).last))
val greater = keyset.filter(_ > key)
if(!greater.isEmpty) array = array ++ Array(map(greater.toList.sortWith(_ < _).head))
array
}
}
A small bit of functional way
val map =Map(1->"a",2->"b",3->"c",5->"d",9->"e",100->"z")
val keyset = map.keySet
def getNearestValues(key: Int) : Array[String] = keyset.contains(key) match {
case true => Array(map(key))
case false => {
val (lower, upper) = keyset.toList.sortWith(_ < _).span(x => x < key)
val lowArray = if(lower.isEmpty) Array.empty[String] else Array(map(lower.last))
val upperArray = if(upper.isEmpty) Array.empty[String] else Array(map(upper.head))
lowArray ++ upperArray
}
}
getNearestValues(0) should return Array(a) and getNearestValues(50) should return Array(e, z) and getNearestValues(9) should return Array(e)
You can solve this problem with the complexity smaller that in any proposed solutions above . So, if performance is critical, check this answer.
Another Scala solution
val m = Map(1 -> "a", 2 -> "b", 3 -> "c", 5 -> "d", 9 -> "e", 100 -> "z")
List(0, 2, 50, 101).foreach { i => {
val inp = i
val (mn, mx) = if (m.get(inp).nonEmpty) (Map(inp -> m(inp)), Map(inp -> m(inp))) else m.partition(x => x._1 > inp)
(mn, mx) match {
case (x, y) if y.isEmpty => println(m(mn.keys.min))
case (x, y) if x.isEmpty => println(m(mx.keys.max))
case (x, y) if y == x => println(m(inp))
case (x, y) => println(m(mn.keys.min), m(mx.keys.max))
}
}
}
Results:
a
b
(z,e)
z

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 }

Strange behaviour in mapValues when returning an option

I have defined the object below. But I don't understand why the mapValues body only executes in test1. ie. why is the output:
Calling test1
Calling test2
Mapping: One
Mapping: Two
Mapped: Map(1 -> Xx, 2 -> Xx)
I have tested it with both scala 2.10 and 2.11 with the same results.
object Test {
def test1: Option[String] = {
val map = Map(1 -> "One", 2 -> "Two")
val mapped = map.mapValues { v =>
println("Mapping: " + v)
"Xx"
}
None
}
def test2: Option[String] = {
val map = Map(1 -> "One", 2 -> "Two")
val mapped = map.mapValues { v =>
println("Mapping: " + v)
"Xx"
}
println("Mapped: " + mapped)
None
}
def main(args: Array[String]): Unit = {
println("Calling test1")
test1
println("Calling test2")
test2
}
}
mapValues actually returns a view, so the results are computed lazily. From the scaladoc for mapValues:
return a map view which maps every key of this map to f(this(key)). The resulting map wraps the original map without copying any elements.
So for example:
val mapped = Map(1 -> "One", 2 -> "Two").mapValues { v =>
println("Mapping: " + v)
"Xx"
}
On it's own this will print nothing when declared. But as soon as mapped is accessed, the values will be computed, and the statements will be printed. (In fact, the values will be re-computed every time you access mapped)
In Test.test1, there is nothing accessing mapped, so the values are never computed.
In Test.test2, you're printing out mapped, which triggers the computation of the values.
The other answer explains the problem, but as a solution, if you want a strict map, just use normal map:
val m = Map(1 -> "One", 2 -> "Two")
val mapped = m.map {
case (k,v) => k -> {
println("Mapping: " + v)
"Xx"
}
}
Alternatively, you can define your own extension method to do what you want:
import scala.collection.GenTraversableLike
import scala.collection.generic.CanBuildFrom
implicit class HasMapVals[T, U, Repr](val self: GenTraversableLike[(T, U), Repr]) extends AnyVal {
def mapVals[R, That](f: U => R)(implicit bf: CanBuildFrom[Repr, (T, R), That]) = {
self.map { case (k,v) => k -> f(v) }
}
}
val m = Map(1 -> "One", 2 -> "Two")
val mapped = m.mapVals { v =>
println("Mapping: " + v)
"Xx"
}

Scala: why does a `for` comprehension on a Map sometimes yield a List?

Why, in the below code example, does isAList's for comprehension yield a List, but the other two yield Maps? I can't think of any reason - the only difference seems to be that there is isAList's comprehension declares two variables, and the others declare one or zero.
object Weird {
def theMap: Map[Int, String] =
Map(1 -> "uno", 2 -> "dos", 3 -> "tres")
def main(args: Array[String]) {
val isAMap = for {
(key, value) <- theMap
} yield (key*2 -> value*2)
val isAlsoAMap = for {
(key, value) <- theMap
doubleKey = key*2
} yield (doubleKey -> value*2)
val isAList = for {
(key, value) <- theMap
doubleKey = key*2
doubleValue = value*2
} yield (doubleKey -> doubleValue)
println(isAMap)
println(isAlsoAMap)
println(isAList)
}
}
outputs
Map(2 -> unouno, 4 -> dosdos, 6 -> trestres)
Map(2 -> unouno, 4 -> dosdos, 6 -> trestres)
List((2,unouno), (4,dosdos), (6,trestres))
I am comparatively new to Scala, so apologies if I'm being incredibly naive about something!
Recently discussed on the ML:
https://groups.google.com/forum/#!msg/scala-internals/Cmh0Co9xcMs/D-jr9ULOUIsJ
https://issues.scala-lang.org/browse/SI-7515
Suggested workaround is to use a tuple to propagate the variables.
scala> for ((k,v) <- theMap; (dk,dv) = (k*2,v*2)) yield (dk,dv)
res8: scala.collection.immutable.Map[Int,String] = Map(2 -> unouno, 4 -> dosdos, 6 -> trestres)
More on the tupling mechanism:
What are the scoping rules for vals in Scala for-comprehensions