1

I have a Scala Array of 2-tuples like this:

(("A", "2015-11-01"), ("B", "2016-11-11"), ("A", "2017-11-01"), ("B", "2013-11-11"))

I want to create a Map where the key maps to the latest date. So, in the example above, the result should be:

Map ("A" -> "2017-11-01", "B" -> "2016-11-11")

I know how to do it iteratively - but what would be a Scala-way (functional-way) to do this?

Darth.Vader
  • 5,079
  • 7
  • 50
  • 90

2 Answers2

3

First groupBy key and then pick latest Date.

arr
  .groupBy(_._1)
  .map { case (k, v) => k -> v.maxBy(_._2)._2 }

use mapValues to make it shorter

arr.groupBy(_._1).mapValues(_.maxBy(_._2)._2)

As date (string) is formatted properly max date is the latest date. You need not convert date into time in millis to decide the max date.

Scala REPL

scala> val arr = Array(("A", "2015-11-01"), ("B", "2016-11-11"), ("A", "2017-11-01"), ("B", "2013-11-11"))
arr: Array[(String, String)] = Array((A,2015-11-01), (B,2016-11-11), (A,2017-11-01), (B,2013-11-11))

scala> :paste
// Entering paste mode (ctrl-D to finish)

arr
  .groupBy(_._1)
  .map { case (k, v) => k -> v.maxBy(_._2)._2 }


// Exiting paste mode, now interpreting.

res0: scala.collection.immutable.Map[String,String] = Map(A -> 2017-11-01, B -> 2016-11-11)

date conversion is not needed but if you wish to convert it then go ahead.

date conversion:

//ensure correct date format is given to this method if not it will throw match error at runtime.
def convertStringDateToMillis(str: String): Long = {
 val regex = "(\\d{4})-(\\d{2})-(\\d{2})".r.unanchored
 val regex(year, month, day) = str
 val calendar = Calendar.getInstance()
 calendar.clear()
 calendar.set(Calendar.MONTH, month.toInt)
 calendar.set(Calendar.YEAR, year.toInt)
 calendar.set(Calendar.DAY_OF_MONTH, month.toInt)
 calendar.getTimeInMillis();
}

Solution:

val arr = Array(("A", "2015-11-01"), ("B", "2016-11-11"), ("A", "2017-11-01"), ("B", "2013-11-11"))

arr.groupBy(_._1).map { case (k, v) => k -> v.maxBy(convertStringDateToMillis(_._2))._2 }

Scala REPL

scala> def convertStringDateToMillis(str: String): Long = {
     |  val regex = "(\\d{4})-(\\d{2})-(\\d{2})".r.unanchored
     |  val regex(year, month, day) = str
     |  val calendar = Calendar.getInstance()
     |  calendar.clear()
     |  calendar.set(Calendar.MONTH, month.toInt)
     |  calendar.set(Calendar.YEAR, year.toInt)
     |  calendar.set(Calendar.DAY_OF_MONTH, month.toInt)
     |  calendar.getTimeInMillis();
     | }
convertStringDateToMillis: (str: String)Long

scala> val arr = Array(("A", "2015-11-01"), ("B", "2016-11-11"), ("A", "2017-11-01"), ("B", "2013-11-11"))
arr: Array[(String, String)] = Array((A,2015-11-01), (B,2016-11-11), (A,2017-11-01), (B,2013-11-11))


scala> arr.groupBy(_._1).map { case (k, v) => k -> v.maxBy(x => convertStringDateToMillis(x._2))._2 }
res3: scala.collection.immutable.Map[String,String] = Map(A -> 2017-11-01, B -> 2016-11-11)
Nagarjuna Pamu
  • 14,737
  • 3
  • 22
  • 40
  • when I do a "arr.groupBy(_._1)" it complains about the "_1" part and says "value _1 is not a member of Product with Serializable". The "arr" is of type ArrayBuffer.. so, I do a "arr.toArray" before I call the groupBy function, but still I see this error. Thanks. – Darth.Vader Nov 05 '16 at 21:37
  • 1
    @Darth.Vader .. your array should be this `val arr = Array(("A", "2015-11-01"), ("B", "2016-11-11"), ("A", "2017-11-01"), ("B", "2013-11-11"))` – Nagarjuna Pamu Nov 05 '16 at 21:40
  • my arr variable looks like this: Array((A, 2015-11-01), (B, 2016-11-11), (A, 2017-11-01), (B, 2013-11-11)) – Darth.Vader Nov 06 '16 at 14:44
  • Here is the question I asked about this: http://stackoverflow.com/questions/40451007/create-a-map-from-list-in-scala – Darth.Vader Nov 06 '16 at 15:25
0

Something like this should work:

array
  .groupBy(_._1)
  .mapValues(_.map(_._2).max)
Dima
  • 39,570
  • 6
  • 44
  • 70