Add auto incremented values to scala map for null values

Viewed 155

I have Scala map like below:

val mapData = Map(AA1 -> 1, AA2 -> 2, AA3 -> null, AA4 -> null)

I want to auto increment the value from highest map value(in above map it is "2").

val maxValue = mapData.valuesIterator.max

I want to iterate through this map and if the value is null substitute the value with the auto incremented value starting from highest map value(in above map it is 2). Result should be like below. Order is not important.

Map(AA1 -> 1, AA2 -> 2, AA3 -> 3, AA4 -> 4)

I tried using mapData.map() but I couldn't persist the incremented value during the map operation. Is there any other way in Scala to achieve this?

3 Answers

First of all, the code snippet you provided won't compile, because of Null in values and keys not swapped in quotes to be considered as string. I've replaced values with Option:

val mapData: Map[String, Option[Int]] = Map("AA1" -> Some(1), "AA2" -> Some(2), "AA3" -> None, "AA4" -> None)

val max = mapData.values.flatten.max
val (result, _) = mapData.toList.foldLeft(Map.empty[String, Int] -> (max + 1)) {
  case ((accumulator, nextMax), (key, None)) =>
    ((accumulator + (key -> nextMax)), nextMax + 1)
  case ((accumulator, nextMax), (key, Some(value))) =>
    ((accumulator + (key -> value)), nextMax)
}

println(result)

which will print next result: Map(AA1 -> 1, AA2 -> 2, AA3 -> 3, AA4 -> 4) Scatie example: https://scastie.scala-lang.org/ltmhh3tuQCWf2deaLboK9w

UPDATE: As @Tomer Shetah correctly stated in comments section - max is not safe, because List[Int].empty.max will throw exception. Replaced with maxOption

val mapData: Map[String, Option[Int]] = Map("AA1" -> Some(1), "AA2" -> Some(2), "AA3" -> None, "AA4" -> None)

val max = mapData.values.flatten.maxOption.getOrElse(0) //safer with maxOption
val (result, _) = mapData.toList.foldLeft(Map.empty[String, Int] -> (max + 1)) {
  case ((accumulator, nextMax), (key, None)) =>
    ((accumulator + (key -> nextMax)), nextMax + 1)
  case ((accumulator, nextMax), (key, Some(value))) =>
    ((accumulator + (key -> value)), nextMax)
}

println(result)

Scatie: https://scastie.scala-lang.org/iiSvahIZStyL1MDi0Glsvg

Here is a basic approach but might not a optimized one.

var mapData = Map("AA1" -> 1, "AA2" -> 2, "AA3" -> null, "AA4" -> null)

 val nonNull = mapData.values.filter(_ != null)
 val max = nonNull.map(_.toString.toInt).max
 var nullCount = mapData.count(_._2==null)

 mapData=mapData.map(t => {
 if (t._2 == null) {
    val output = (t._1, max + nullCount)
    nullCount -= 1
    output
}
else (t._1, t._2)
})
println(mapData)
   
//output : Map(AA1 -> 1, AA2 -> 2, AA3 -> 4, AA4 -> 3)

Another option you have is:

val startFrom = mapData.values.filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int]).maxOption.getOrElse(0) + 1
val values = LazyList.from(startFrom).iterator
val result = mapData.view.mapValues {
  case x: Int => x
  case null => values.next()
  case _ => ???
}

Please note that result is not materialized. You need to call .toMap to materialize it.

Code run at Scastie

Related