Search code examples
kotlinfunctional-programming

How can I use elegant kotlin techniques to solve this problem? (FP)


I have a kotlin problem, can you come up with a elegant way of solving it?

so effectively I have a list of Journeys which could be by car or not, I want to find where there are consecutive car journeys in a list; i.e. 2 or more cars in a row in the list. Then from these chains of consecutive cars I wan to sum the mileage per chain and print out the maximum. This is the data class

  data class Journey(
    val isCar: Boolean = false,
    val mileage: Int,
  )

Here is an example list of journeys. So in this example the correct answer should be 80, because there are two consecutive entries with 40+40 as the mileage. That beats the only other consecutive entry of 10+20=30. The rest are single car journeys and non car journeys so are ignored.

listOf(
      Journey(true, 10),
      Journey(true, 20),
      Journey(false, 105),
      Journey(true, 1046),
      Journey(false, 130),
      Journey(true, 40),
      Journey(true, 40),
    )

My solution is below, however it's very ugly and non-functional. Can you solve this in an elegant way using modern functional techniques without mutable Lists? Thanks

val journeys = listOf(
      Journey(true, 10),
      Journey(true, 20),
      Journey(false, 105),
      Journey(true, 1046),
      Journey(false, 130),
      Journey(true, 40),
      Journey(true, 40),
    )

    val cars = journeys.filter { it.isCar }
    val journeysByIndex = cars.map { journeys.indexOf(it) to it  }
    val consecutiveJourneys = mutableListOf<MutableList<Journey>>()

    journeysByIndex.forEach {
      if (consecutiveJourneys.any { cj1 -> cj1.any { cj2 -> cj2 == it.second  } }) {
        return@forEach
      }
      val journeyList = mutableListOf(it.second)
      var index = it.first
      var nextJourneyExists = true
      while (nextJourneyExists) {
        var journey: Journey? = null
        if ((index + 1) < journeys.size  && journeys[index + 1].isCar) {
          journey = journeys[index+1]
        }
        nextJourneyExists = if (journey!= null) {
          journeyList.add(journey)
          true
        } else {
          false
        }
        index += 1
      }
      consecutiveJourneys.add(journeyList)
    }

    var maxAmountForConsecutive = consecutiveJourneys.filter {it.size > 1 }.map{ it.sumOf {cs -> cs.mileage}}.max()

    println("max1: " + maxAmountForConsecutive)

Solution

  • Besides the grouping method that Tenfour04 suggested in the comment, which I personally find a bit overkill for a task like this (but it is useful in other scenarios), you could also just simply count.

    We can create a simple extension function that counts each step, then resets once it hits a isCar:false:

    fun Collection<Journey>.calculateMaxByCarConsecutive(): Int {
        var max = 0
        var currentMileageSum = 0
        var count = 0
    
        forEach { current ->
            if (current.isCar) {
                //if the current journey is in a car, add its mileage to our current count
                currentMileageSum += current.mileage
                //increase count that tells us how many journeys have been consecutive
                count++
    
                //if we have more than 1 journey creating the currentMileageSum 
                //and 
                //currentMileageSum is greater than max, 
                // then the currentMileageSum becomes the new max
                if (count > 1) max = maxOf(currentMileageSum, max)
            } else {
                //reset the counters if this record is not by car
                count = 0
                currentMileageSum = 0
            } 
        }
    
        return max
    }
    

    Then we can call it like this:

    val journeys = listOf(
        Journey(true, 10),
        Journey(true, 20),
        Journey(true, 40),
        Journey(false, 105),
        Journey(true, 10423423),
        Journey(false, 130),
        Journey(true, 40),
        Journey(true, 40),
        Journey(true, 10),
    )
    
    val maxJourneyByCar = journeys.calculateMaxByCarConsecutive()
    
    println(maxJourneyByCar) //this will print 90
    
    

    Not sure if this is more elegant that your solution or than using fold, but I do think it is simpler.