Home > Back-end >  How to get longest streak from an array of int Kotlin
How to get longest streak from an array of int Kotlin

Time:05-25

I am working on a story and am wondering how I can solve this problem similar to my current work at work, so a weekly streak is defined as one or more consecutive active weeks in which a user has worked out. So say you are given an array of active user days, we need find the length of the longest active weekly streak. Here each element in the array represents an offset from an arbitrary Monday in the calendar (e.g. Thursday Feb 4th 2022).

The offsets are zero indexed. Some helpful tips are:

  • Active day there is at least one exercise for the user during a calendar day
  • Active week is a calendar week, starting on a Monday, with at least one active day for a user
  • Weekly streak being One or more weeks (consecutive) active weeks in a user's workout history The length of the streak is defined as the number of active weeks in the streak

Here is my code:

val str1 = arrayOf(9, 14, 365, 366, 367) //2
val str2 = arrayOf(2, 22, 29, 37, 43, 366, 391) //4

fun findLongestStreak(callDays: Array<Int>): Int{

}

What is the simplest way to write this function I have been stuck for a week now?

CodePudding user response:

You could do it like this

val str1 = arrayOf(9, 14, 365, 366, 367) //2
val str2 = arrayOf(2, 22, 29, 37, 43, 366, 391) //4

fun toWeek(day: Int) = day / 7
fun activeWeeks(days: Array<Int>) = days.map(::toWeek).toSet().sorted()

// find runs of consecutive numbers in a list of integers,
// and return a list of those runs' lengths
fun getStreaks(nums: List<Int>): List<Int> = 
    if (nums.isEmpty()) emptyList()
    // We're going to iterate over each adjacent pair of numbers, so we can check
    // if the second number immediately follows the first, so we can add to
    // the current streak.
    
    // Since we're looking at the second number in each pair, the first in the list
    // (we know there's at least one) is a special case - so we start the list
    // we're building up with a streak of 1, representing that first number
    else nums.zipWithNext().fold(mutableListOf(1)) { streaks, (prev, current) ->
        streaks.apply {
            // if the second number (the one we're checking) follows the first,
            // add 1 to the most recent streak length in the list
            if (current == prev   1) streaks[lastIndex]  = 1
            // if they're not consecutive, we need to start a new streak for this number
            else streaks.add(1)
        }
    }

fun findLongestStreak(callDays: Array<Int>): Int =
    callDays.run(::activeWeeks)
        .run(::getStreaks)
        .run { maxOrNull() ?: 0 }

That's mostly explanation - basically I'm converting all the day numbers into their corresponding week numbers, using toSet() to remove duplicates and sorted to turn that into a List of ascending week numbers

Then the fold function starts with a streak of 1 for the first week number, then goes through the rest of them, comparing each to the previous week number. If it's consecutive, add 1 to the current streak. If it's not, start a new streak of length 1. (If you don't like the fold, you could just do a for loop with the same logic)

That way you end up with a list of streak lengths and you can just grab the longest one

CodePudding user response:

You can use a Set that has a unique values for the workout weeks. Then filter the set with non-consecutive weeks; the size of the outcome is what you need (the active discrete weeks):

fun findLongestStreak(callDays: Array<Int>): Int {
    val workoutWeeks = mutableSetOf<Int>() // all the workout weeks
    for (day in callDays) workoutWeeks.add(day / 7)
    return workoutWeeks.filter { key -> !workoutWeeks.contains(key - 1) }.size
}

Notice that the day / 7 returns the week number offset similarly referring as they are offset to the base Monday.

  • Related