Search code examples
scalafold

scala (scanLeft) - how to (functionally) get a Map with cumulated values / frequency


I am new to Scala and trying to get a Map having cumulative frequencies as values from a Map with the individual ones. So (for an immutable Map persisting the order such as ListMap) value(i) is the sum of all values up to and including i in the original Map as such:

val map = ListMap(0.0 -> 0.1, 0.01 -> 0.2, 0.05 -> 0.3, 0.1 -> 0.4)
val resultMap = ListMap(0.0 -> 0.1, 0.01 -> 0.3, 0.05 -> 0.6, 0.1 -> 1.0)

Since one would need access to previous key-value pair I came up with an iterative implementation which makes use of mutable.LinkedHashMap and a running total, but I find that messy and still looking for a way to get a mutable map from the immutable one (as in line3 below):

var cummulativeMap0 = mutable.LinkedHashMap.empty[Double, Double]
var total = 0.0
//line3:
val copyMap = mutable.LinkedHashMap(0.0->0.1, 0.01->0.2, 0.05->0.3, 0.1->0.4) 
map.foreach {
      (kv) => { 
          total = total + copyMap.remove(kv._1).get
          cummulativeMap0.put(kv._1 , total) 
          } 
       }
cummulativeMap0

Alternatively I was able to use scanLeft but only with the cost of splitting the keys and values in 2 Lists to be zipped later:

val cummValues = map.values.scanLeft(0.0){ (a, b)=>a+b }.tail
val cummmulativeMap2 = (map.keys zip cummValues).toMap

What is the most idiomatic/functional way to achieve this, is it doable with scanLeft directly on the Map? Please help


Solution

  • If you don't want to zip-unzip it, just ignore the previous key in each step:

    import scala.collection.immutable._
    
    val map = ListMap(0.0 -> 0.1, 0.01 -> 0.2, 0.05 -> 0.3, 0.1 -> 0.4)
    val res = map.scanLeft((0.0, 0.0)){
      case ((_, acc), (x, y)) => (x, acc + y)
    }
    
    println(res)
    

    Gives you:

    ListMap(0.0 -> 0.1, 0.01 -> 0.3, 0.05 -> 0.6, 0.1 -> 1.0)
    

    (up to machine precision, truncated some zeroes from output)