Search code examples
scalafunctional-programming

How can I rewrite this function in a functional style of Scala?


I'm trying to rewrite a function in a functional style. I know the function is bad. But how can I avoid the usage of the var field watermarkTime?

The function sets day "1" for the rows with a continuous increasing timestamp. If the timestamp is lesser than the timestamp of the previous row, then set day "2".

case class Row(time: String, name: String, var day: Int)

val rows = List(new Row("09:18:52", "3_0711-082757_01001", 0),
                new Row("09:23:18", "3_0711-082757_01002", 0),
                new Row("09:33:43", "3_0711-082757_01004", 0),
                new Row("10:20:00", "3_0711-082757_01011", 0),
                new Row("05:03:38", "3_0711-082757_02001", 0),  // set secound day
                new Row("05:07:51", "3_0711-082757_02002", 0),
                new Row("05:13:02", "3_0711-082757_02003", 0),
                new Row("05:19:16", "3_0711-082757_02004", 0),
                new Row("10:54:27", "3_0711-082757_02015", 0),  // set first day 
                new Row("11:00:38", "3_0711-082757_02016", 0),
                new Row("11:07:28", "3_0711-082757_02017", 0))


def setDayFP(rows: List[Row]): List[Row] = {

  var watermarkTime = 0

  for (row <- rows) {

    val newTime = row.time.replaceAll(":","").toInt 

    if (watermarkTime < newTime) {
      watermarkTime = newTime
      row.day = 1
    }  else  {
      row.day = 2
    }
  }
  return rows 
}

Here is the result (sorted by day and name):

Row(09:18:52,3_0711-082757_01001,1)
Row(09:23:18,3_0711-082757_01002,1)
Row(09:33:43,3_0711-082757_01004,1)
Row(10:20:00,3_0711-082757_01011,1)
Row(10:54:27,3_0711-082757_02015,1)
Row(11:00:38,3_0711-082757_02016,1)
Row(11:07:28,3_0711-082757_02017,1)
Row(05:03:38,3_0711-082757_02001,2)
Row(05:07:51,3_0711-082757_02002,2)
Row(05:13:02,3_0711-082757_02003,2)
Row(05:19:16,3_0711-082757_02004,2)

I'm looking for a better solution. Thanks in advance!


Solution

  • The solution below

    • has an immutable list class and uses copying instead of field updates

    • uses foldLeft instead of a loop

    • uses a tuple as the fold's aggregator to carry around the watermarkTime

    However, I personally would stick with your for-loop and the var watermarkTime, since the foldLeft doesn't make it more readable. I would keep the day: Option[Int] and the copying, though.

    case class Row(time: String, name: String, day: Option[Int] = None)
    
    val rows = List(new Row("09:18:52", "3_0711-082757_01001"),
                    new Row("09:23:18", "3_0711-082757_01002"),
                    new Row("09:33:43", "3_0711-082757_01004"),
                    new Row("10:20:00", "3_0711-082757_01011"),
                    new Row("05:03:38", "3_0711-082757_02001"),
                    new Row("05:07:51", "3_0711-082757_02002"),
                    new Row("05:13:02", "3_0711-082757_02003"),
                    new Row("05:19:16", "3_0711-082757_02004"),
                    new Row("10:54:27", "3_0711-082757_02015"),
                    new Row("11:00:38", "3_0711-082757_02016"),
                    new Row("11:07:28", "3_0711-082757_02017"))
    
    
    def setDayFP(rows: List[Row]): List[Row] = {
      rows.foldLeft((0, List[Row]()))
                   {case ((watermarkTime, resultRows), row) =>
    
        val newTime = row.time.replaceAll(":","").toInt
    
        val (newWatermarkTime, day) = 
          if (watermarkTime < newTime)
            (newTime, 1)
          else
            (watermarkTime, 2)
    
        (newWatermarkTime, row.copy(day = Some(day)) :: resultRows)
      }._2.reverse
    }
    
    setDayFP(rows).foreach(println)