map reduce sum item weights in a string

Viewed 184

I have a string like the following:

s =  "eggs 103.24,eggs 345.22,milk 231.25,widgets 123.11,milk 14.2" 

such that a pair of item and its corresponding weights is separated by a comma, and the item name and its weight is by a space. I want to get the sum of the weights for each item:

//scala.collection.immutable.Map[String,Double] = Map(eggs -> 448.46, milk -> 245.45, widgets -> 123.11)

I have done the following but got stuck on the steps of separating out the item and its weight:

s.split(",").map(w=>(w,1)).sortWith(_._1 < _._1)

//Array[(String, Int)] = Array((eggs 345.22,1), (milk 14.2,1), (milk 231.25,1), (widgets 103.24,1), (widgets 123.11,1))

I think to proceed, for each element in the array I need to separate out the item name and weight separated by space, but when I tried the following I got quite confused:

  s.split(",").map(w=>(w,1)).sortWith(_._1 < _._1).map(w => w._1.split(" ") )
  //Array[Array[String]] = Array(Array(eggs, 345.22), Array(milk, 14.2), Array(milk, 231.25), Array(widgets, 103.24), Array(widgets, 123.11))

I am not sure what the next steps should be to proceed the calculations.

2 Answers

If you guaranteed to have the string in this format (so no exceptions and edge cases handling) you can do something like that:

val s =  "eggs 103.24,eggs 345.22,milk 231.25,widgets 123.11,milk 14.2" 
val result = s
  .split(",") // array of strings like "eggs 103.24"
  .map(_.split(" ")) // sequence of arrays like ["egg", "103.24"]
  .map { case Array(x, y) => (x, y.toFloat)} // convert to tuples (key, number)
  .groupBy(_._1) // group by key
  .map(t => (t._1, t._2.map(_._2).sum)) // process groups, results in Map(eggs -> 448.46, ...)

Similar to what @GuruStron proposed, but handling possible errors (by just ignoring any kind of malformed data).
Also this one requires Scala 2.13+, older versions won't work.

def mapReduce(data: String): Map[String, Double] =
  data
    .split(',')
    .iterator
    .map(_.split(' '))
    .collect {
      case Array(key, value) =>
        key.trim.toLowerCase -> value.toDoubleOption.getOrElse(default = 0)
    }.toList
    .groupMapReduce(_._1)(_._2)(_ + _)
Related