Scala - Get the nearest key value from both sides - scala

I'm working on getting the nearest key value in both ways with a given input integer.
Example:
The defined map is as below.
val m = Map(10 -> "W",20 -> "W",30 -> "I",40 -> "A",50 -> "P",60 -> "S",70 -> "A",80 -> "A",90 -> "A",100 -> "I",110 -> "A",120 -> "E")
The keys are integers and they increase in order.
If say, 95 is given as input, then I should get the tuple output as follows
((90->"A"), (100->"I"))
scala> m.map( x=> (x._1,x._2)).toList.sortBy(_._1).filter( _._1<=95 ).last
res74: (Int, String) = (90,A)
scala> m.map( x=> (x._1,x._2)).toList.sortBy(_._1).filter( _._1>=95 ).head
res75: (Int, String) = (100,I)
scala>
The Map size will be big in real scenario(1K) and I want to avoid the sortBy(). Are there any possible foldLeft or map solutions available to this?.

Here's a pretty straight forward solution. No sorting required.
def nearest[V](m :Map[Int,V], k :Int) :Seq[(Int,V)] =
m.get(k).fold {
val (before, after) = m.keys.partition(_ < k)
Seq(before.maxOption, after.minOption).flatten.map(x => (x,m(x)))
}(v => Seq((k,v)))
testing:
val m = Map(10 -> "W",20 -> "W",30 -> "I",40 -> "A",50 -> "P",60 -> "S",70 -> "A",80 -> "A",90 -> "A",100 -> "I",110 -> "A",120 -> "E")
nearest(m, -7) //res0: Seq[(Int, String)] = List((10,W))
nearest(m, 60) //res1: Seq[(Int, String)] = List((60,S))
nearest(m, 93) //res2: Seq[(Int, String)] = List((90,A), (100,I))
nearest(m, 121) //res3: Seq[(Int, String)] = List((120,E))

Here's one approach that leverages TreeMap's sorting and range query features as shown below:
def nearestValues(m: Map[Int, String], key: Int) = {
import scala.collection.immutable.TreeMap
val tm = TreeMap(m.toSeq: _*)
Seq(tm.to(key).lastOption, tm.from(key).headOption).flatten.distinct
}
val m = Map(
10 -> "W", 20 -> "W", 30 -> "I", 40 -> "A", 50 -> "P", 60 -> "S",
70 -> "A", 80 -> "A", 90 -> "A", 100 -> "I", 110 -> "A", 120 -> "E"
)
nearestValues(m, 95)
// res1: Seq[(Int, String)] = List((90,A), (100,I))
nearestValues(m, 20)
// res2: Seq[(Int, String)] = List((20,W))
nearestValues(m, 125)
// res3: Seq[(Int, String)] = List((120,E))
Note that the above method returns a Seq rather than a Tuple to accommodate cases of exact or one-sided matches. To return a Tuple, one could go with something similar to the following:
def nearestValues(m: Map[Int, String], key: Int) = {
import scala.collection.immutable.TreeMap
val tm = TreeMap(m.toSeq: _*)
Seq(tm.to(key).lastOption, tm.from(key).headOption) match {
case Seq(None, None) => (0 -> "", 0 -> "") // Default tuple for empty Map
case Seq(x, None) => (x.get, Int.MaxValue -> "")
case Seq(None, y) => (Int.MinValue -> "", y.get)
case Seq(x, y) => (x.get, y.get)
}
}
nearestValues(m, 95)
// res1: ((Int, String), (Int, String)) = ((90,A),(100,I))
nearestValues(m, 20)
// res2: ((Int, String), (Int, String)) = ((20,W),(20,W))
nearestValues(m, 125)
// res3: ((Int, String), (Int, String)) = ((120,E),(2147483647,""))
UPDATE:
Starting Scala 2.13, methods to and from for TreeMap are replaced with rangeTo and rangeFrom, respectively.

You can use sorted collection as Leo C proposed, however that requires collection construction and then search, so complexity of such algorithm will be O(n*log n).
Approaching this task in algorithmic way, you can calculate the difference for the keys and then find the closest negative and positive values to 0. In this case complexity will be lower O(n).
Below example returns nearest keys from the left and the right excluding exact match (you may change that by changing a condition in the filter):
val data = Map(1-> "a", 5->"b")
val key = 4
val diffs = data.keys.map(_ - key)
val rightKeyOpt = diffs.filter(_ > 0).reduceOption(_ min _).map(_ + key)
val leftKeyOpt = diffs.filter(_ < 0).reduceOption(_ max _).map(_ + key)
val result = (leftKeyOpt.map(k=> k->data(k)), rightKeyOpt.map(k=> k->data(k)))
println (leftKeyOpt, rightKeyOpt)
println result
I bet this could be done in a single line if you need that very much:)

Related

Convert Seq[Option[Map[String, Any]]] to Option[Map[String, Any]]

How to Convert Seq[Option[Map[String, Any]]] to Option[Map[String, Any]]
So skip any option that is None, and keep the valid Maps and merge them. If every option is None, then the final option should be None as well.
A flatMap along with groupMapReduce, followed by an Option filter should do the job:
val listOfMaps: List[Option[Map[String, Any]]] =
List(Some(Map("a"->"p", "b"->2)), None, Some(Map("a"->"q", "c"->"r")))
val mergedMap = listOfMaps.
flatMap(_.getOrElse(Map.empty[String, Any])).
groupMapReduce(_._1)(t => List[Any](t._2))(_ ::: _)
// mergedMap: Map[String, List[Any]] =
// Map("a" -> List("p", "q"), "b" -> List(2), "c" -> List("r"))
Option(mergedMap).filter(_.nonEmpty)
// res1: Option[Map[String, List[Any]]] =
// Some(Map("a" -> List("p", "q"), "b" -> List(2), "c" -> List("r")))
A few notes:
groupMapReduce is available only on Scala 2.13+.
If you must stick to Seq instead of List, simply replace method ::: with ++ in groupMapReduce.
It is assumed that merging of the Maps means aggregating the Map values of a common key into a List. Replace groupMapReduce with toMap if keeping only one of the Map values of a common key is wanted instead.
This solution treats Some(Map(.empty[String, Any])) the same as None.
This is a one-line solution
val in: Seq[Option[Map[String, Any]]] = ???
val out: Option[Map[String, Any]] =
in.flatten.headOption.map(_ => in.flatten.reduce(_ ++ _))
The in.flatten.headOption is a simple way of getting Some if at least one of the elements is Some or None if they are all None.
The reduce just combines all the Maps into one.
This can clearly be optimised by avoiding the duplicate in.flatten call.
Here is an example with flatten,fold, ++ and a match at the end to provide Some or None.
baz.scala
package baz
object baz {
// fixtures
val x0 = Seq[Option[Map[String, Any]]]()
val x1 = Seq[Option[Map[String, Any]]](None)
val x2 = Seq[Option[Map[String, Any]]](Some(Map("a" -> 1, "b" -> "two")))
val x3 = Seq[Option[Map[String, Any]]](Some(Map("a" -> 1, "b" -> "two")), Some(Map("c" -> 3.0)), None)
def f(x: Seq[Option[Map[String, Any]]]) =
x.flatten.fold(Map[String, Any]())((a,b) => a ++ b) match { case m if m.isEmpty => None case m => Some(m) }
}
Sample run
bash-3.2$ scalac baz.scala && scala -classpath .
Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_66).
Type in expressions for evaluation. Or try :help.
scala> import baz.baz._
import baz.baz._
scala> x0 -> f(x0)
res0: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(),None)
scala> x1 -> f(x1)
res1: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(None),None)
scala> x2 -> f(x2)
res2: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(Some(Map(a -> 1, b -> two))),Some(Map(a -> 1, b -> two)))
scala> x3 -> f(x3)
res3: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(Some(Map(a -> 1, b -> two)), Some(Map(c -> 3.0)), None),Some(Map(a -> 1, b -> two, c -> 3.0)))
scala> :quit

How to calculate running total for a SortedMap / TreeMap

So I have the following SortedMap:
val mySortedMap: SortedMap[Double, Int] = SortedMap(1.1 -> 7, 2.4 -> 3, 6.5 -> 12)
Now I need to calculate the running total for each key, so the output should look like this:
val result: SortedMap[Double, Int] = SortedMap(1.1 -> 7, 2.4 -> 10, 6.5 -> 22)
I know I can do something similar by using scanLeft:
val result: Iterable[Int] = mySortedMap.scanLeft(0)((c, e) => c + e._2)
But this returns Iterable whereas I need to keep my SortedMap as specified above. What is the most functional / efficient way to do so?
You can use foldLeft where your accumulator is both, a new SortedMap and the running total.
import scala.collection.immutable.SortedMap
def runningTotal(map: SortedMap[Double, Int]): SortedMap[Double, Int] = {
val (transformed, _) = map.foldLeft((SortedMap.empty[Double, Int], 0)) {
(acc, element) =>
val (mapAcc, totalAcc) = acc
val (key, value) = element
val newTotal = totalAcc + value
val newMap = mapAcc + (key -> newTotal)
(newMap, newTotal)
}
transformed
}
Bonus, here is your solution but using Iterators instead, thus it would be a little bit more efficient.
def runningTotal(map: SortedMap[Double, Int]): SortedMap[Double, Int] = {
val newValues = map.valuesIterator.scanLeft(0) {
(acc, value) => acc + value
}.drop(1)
map.keysIterator.zip(newValues).to(SortedMap)
}
Ok, I think I might have found one possible solution:
val input: SortedMap[Double, Int] = SortedMap(1.1 -> 7, 2.4 -> 3, 6.5 -> 12)
val aggregates: Iterable[Int] = mySortedMap.scanLeft(0)((c, e) => c + e._2).tail
val sortedAggregates: SortedMap[Double, Int] = input.keySet.zip(aggregates).to(SortedMap)
println(sortedAggregates)
gives TreeMap(1.1 -> 7, 2.4 -> 10, 6.5 -> 22).
I wonder if there is a better way?

Scala Map Reduction and Aggregation

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.

Updating immutable map as the side effect of getOrElse

Sometimes I use a Map as a memoization cache. With mutable maps, I use getOrElseUpdate:
mutableMap.getOrElseUpdate(key, {
val value = <compute the value>
value
})
Immutable maps don't have getOrElseUpdate. So I want to do this
immutableMap.getOrElse(key, {
val value = <compute the value>
immutableMap += key -> value
value
})
This seems to work in practice, I have good arguments to believe it works in theory, and it's more or less readable -- is it a terrible idea for some reason I'm missing?
The other alternatives I'm considering are
immutableMap.get(key) match {
case Some(value) => value
case None =>
val value = <compute the value>
immutableMap += key -> value
value
}
which is not much different and is more cumbersome, or
if (immutableMap.contains(key)) {
immutableMap(key)
} else {
val value = <compute the value>
immutableMap += key -> value
value
}
which is the dumbest and probably least idiomatic.
In principle I rather not go for a solution that uses a helper to return the value and the updated map, unless it's the unarguably superior way.
Sure, it seems reasonable except for one small issue... it's not updating your collection! If you're using an immutable Map, then that Map is immutable. You can not change it, ever.
In fact, immutable Map from Scala collection does not even have a += method defined on it, see immutable.Map. All the methods with "append" or "add" new values to the Map actually return a new Map. So for what you've written above to compile, you'd have to not be using something immutable.
To do this with an immutable map, you'll need to work with a var and replace that var with the new Map (which can lead to issues with threading) or you have to adopt a State Monad type pattern in which you return not only the new value but also the new Map.
def getOrCalc(m: Map[Key, Value], k: Key)(f: Key => Value): (Map[Key, Value], Value] ={
if(m.contains(k)) (m, m(k))
else{
val value = f(k)
(m +: (k, value), value)
}
}
My only recommendation (regarding the reasons why you choosed var instead of mutable.Map or Java's ConcurrentMap) is to wrap it into DSL, like:
case class Mutable[K,V](var m: Map[K,V]) {
def orElseUpdate(key: K, compute: => V) = m.getOrElse(key, {
val value = compute
m += key -> value
value
})
}
scala> val a = Mutable(Map(1 -> 2))
a: Mutable[Int,Int] = Mutable(Map(1 -> 2))
scala> a.orElseUpdate(2, 4)
res10: Int = 4
scala> a.orElseUpdate(2, 6)
res11: Int = 4
scala> a.orElseUpdate(3, 6)
res12: Int = 6
Another option (if your computation is lightweight) is just:
m += key -> m.getOrElse(key, compute)
m(key)
Example:
scala> var m = Map(1 -> 2)
m: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)
scala> m += 3 -> m.getOrElse(3, 5)
scala> m
res1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 5)
scala> m += 3 -> m.getOrElse(3, 5)
scala> m
res3: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 5)
scala> m += 3 -> m.getOrElse(3, 6)
scala> m
res5: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 5)
You can wrap it into DSL as well:
implicit class RichMap[K,V](m: Map[K,V]) {
def kvOrElse(k: K, v: V) = k -> m.getOrElse(k, v)
}
scala> m += m.kvOrElse(3, 7)
scala> m
res7: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 5)
scala> m += m.kvOrElse(4, 7)
scala> m
res9: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 5, 4 -> 7)

Get value with the lowest key value from Map[Int, String]

Say I have a map: Map[Int, String]. How would I get the value [String] with the lowest key [Int]. I've been trying to implement this functionally, but just can't figure out how to do this.
The following code will get you a value with a lowest key (ignoring some corner cases).
def lowestKeyMember[A](m: Map[Int,A]): A = m(m.keys.min)
This will break ties arbitrarily and throw on an empty map. If you need to do this operation frequently and/or on large maps, you should look into SortedMap.
Maps are not normally sorted. You could however use a SortedMap, then the map will be sorted and the first value will be the head. All you need to do is retrieve the head.
map.head()
Come on, people! "Functionally" is code word for "folding".
scala> val m = Map(1->"eins",2->"zwei",3->"drei")
m: scala.collection.immutable.Map[Int,String] = Map(1 -> eins, 2 -> zwei, 3 -> drei)
scala> m.foldLeft(Int.MaxValue -> "") { case (min,p) => if (min._1 <= p._1) min else p }
res0: (Int, String) = (1,eins)
But an 8-char operator?
Let's see, is that enough parens? Don't tell me -> is like - and /: is like /.
scala> (Int.MaxValue -> "" /: m) { case (min,p) => if (min._1 <= p._1) min else p }
<console>:9: error: missing arguments for method /: in trait TraversableOnce;
follow this method with `_' if you want to treat it as a partially applied function
(Int.MaxValue -> "" /: m) { case (min,p) => if (min._1 <= p._1) min else p }
^
Oh, well, OK.
scala> ((Int.MaxValue -> "") /: m) { case (min,p) => if (min._1 <= p._1) min else p }
res2: (Int, String) = (1,eins)
Or,
scala> import math.Ordering.Implicits._
import math.Ordering.Implicits._
scala> ((Int.MaxValue -> "") /: m) { case (min,p) if min <= p => min case (_, p) => p }
res5: (Int, String) = (1,eins)
A variant of the _.keys.min solution that works with Options (i.e. will not throw on an empty map):
scala> val a : Map[Int, String]=Map(1 -> "1", 2 -> "2")
a: Map[Int,String] = Map(1 -> 1, 2 -> 2)
scala> val b : Map[Int, String]=Map()
b: Map[Int,String] = Map()
scala> def valueForMinKey[K,V](a : Map[K,V])(implicit cmp : Ordering[K]) = a.keys.reduceOption(cmp.min(_, _)).map(a(_))
valueForMinKey: [K, V](a: Map[K,V])(implicit cmp: Ordering[K])Option[V]
scala> valueForMinKey(a)
res27: Option[String] = Some(1)
scala> valueForMinKey(b)
res28: Option[String] = None
In this example, the implicit parameter cmp will be satisfied by Ordering.Int. The example will work with any Map where the keys can be ordered (and a matching implict can be found by the compiler).