3
votes

I have received multiple comma-separated string integers as input, for example, the following strings :

  1. "5,6,0"
  2. "0,1,2"
  3. "1,2,3,4"

Each of these integers is meant to represent a day of the week

  • 0 = Sunday 1 = Monday 2 = Tuesday 3 = Wednesday 4 = Thursday 5 = Friday 6 = Saturday

In the case of the first string, it would mean that Thursday to Sunday The second string would be valid from Sunday to Tuesday The third-string would be valid from Monday to Thursday

Currently, I am using the following

  private fun mapOfDays(validDays: String): LinkedHashMap<Int, String>
    {
        if (!validDays.isBlank())
        {
            val daysArray = validDays.split("\\s*,\\s*") as Array<String>
            var mapDays = LinkedHashMap<Int, String>()
            var mapDay = LinkedHashMap<Int, String>()
            mapDays[0] = "SUNDAY"
            mapDays[1] = "MONDAY"
            mapDays[2] = "TUESDAY"
            mapDays[3] = "WEDNESDAY"
            mapDays[4] = "THURSDAY"
            mapDays[5] = "FRIDAY"
            mapDays[6] = "SATURDAY"

            for (day in daysArray)
            {
                if (mapDays.containsKey(day.toInt()))
                {
                    mapDay[day.toInt()] = mapDays[day.toInt()]!!
                }
            }
            return mapDay
        }
        return LinkedHashMap()
    }

    private fun mappedDays(mapOfDays: LinkedHashMap<Int, String>?): String
    {
        if (!mapOfDays.isNullOrEmpty())
        {
            val mapSize = mapOfDays.size

            if (mapSize > 6) return "All Day"
            if (mapSize > 5) return sixDayString(mapOfDays)
            if (mapSize > 4) return fiveDayString(mapOfDays)
            if (mapSize > 3) return fourDayString(mapOfDays)
            if (mapSize > 2) return threeDayString(mapOfDays)
            if (mapSize > 1) return twoDayString(mapOfDays)
            if (mapSize > 0) return oneDayString(mapOfDays)
        }
        return ""
    }

    private fun twoDayString(mapOfDays: LinkedHashMap<Int, String>): String
    {
        val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
        val lastPosition: Int = mapOfDays.keys.toIntArray()[1]

        val lastDay = Days.values()[lastPosition]
        val firstDay = Days.values()[firstPosition]

        return "$firstDay and $lastDay"
    }

    private fun oneDayString(mapOfDays: LinkedHashMap<Int, String>): String
    {
        var firstPosition: Int = mapOfDays.keys.toIntArray()[0]
        val firstDay = Days.values()[firstPosition]
        return "$firstDay"
    }

    private fun threeDayString(mapOfDays: LinkedHashMap<Int, String>): String
    {
        val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
        val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
        val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]

        val firstDay = Days.values()[firstPosition]
        val secondDay = Days.values()[secondPosition]
        val lastDay = Days.values()[thirdPosition]
        return "$firstDay, $secondDay and $lastDay"
    }

    private fun fourDayString(mapOfDays: LinkedHashMap<Int, String>): String
    {
        val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
        val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
        val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
        val fourthPosition: Int = mapOfDays.keys.toIntArray()[3]

        val firstDay = Days.values()[firstPosition]
        val secondDay = Days.values()[secondPosition]
        val thirdDay = Days.values()[thirdPosition]
        val lastDay = Days.values()[fourthPosition]
        return "$firstDay, $secondDay, $thirdDay and $lastDay"
    }

    private fun fiveDayString(mapOfDays: LinkedHashMap<Int, String>): String
    {
        val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
        val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
        val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
        val fourthPosition: Int = mapOfDays.keys.toIntArray()[3]
        val fifthPosition: Int = mapOfDays.keys.toIntArray()[4]

        val firstDay = Days.values()[firstPosition]
        val secondDay = Days.values()[secondPosition]
        val thirdDay = Days.values()[thirdPosition]
        val fourthDay = Days.values()[fourthPosition]
        val lastDay = Days.values()[fifthPosition]
        return "$firstDay, $secondDay, $thirdDay, $fourthDay and $lastDay"
    }

    private fun sixDayString(mapOfDays: LinkedHashMap<Int, String>): String
    {
        var firstPosition: Int = mapOfDays.keys.toIntArray()[0]
        var lastPosition: Int = 0

        for (day in mapOfDays.keys)
        {
            lastPosition = day
        }

        val lastDay = Days.values()[lastPosition]
        val firstDay = Days.values()[firstPosition]

        return "$firstDay to $lastDay"
    }

}

enum class Days()
{
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

However, my current implementation is on able to tell me which days are included and not able to map out the group of days for example:

If I got "0,1,3,4,5,6" The final string output that I would want to have is the following: Wednesday to Monday

or

"0,1,3,4,5" would lead to the following result: Sunday, Monday, Wednesday to Friday.

2
why "0,1,3,4,5,6" is represnt as Wednesday to Monday? are you sure about this one? - Naor Tedgi
It can be Sunday, Monday, Wednesday to Saturday. - George
First make an array of seven boolean flags corresponding to the days and set the flags for the days in the input. Now you have an ordered "picture" of the whole week that will be easier to scan for ranges of "on" days and "off" days. - Kevin Anderson

2 Answers

1
votes
package days

import java.lang.IllegalArgumentException


class DaysFactory {
    fun dayFromInt(index: Int): Day {
        return when (index) {
            0 -> Day.Sunday
            1 -> Day.Monday
            2 -> Day.Tuesday
            3 -> Day.Wednesday
            4 -> Day.Thursday
            5 -> Day.Friday
            6 -> Day.Saturday
            else -> throw IllegalArgumentException("illigal index :$index")
        }
    }

    enum class Day(val index: Int) {
        Sunday(0), Monday(1), Tuesday(2), Wednesday(3), Thursday(4), Friday(5), Saturday(6)
    }
}


class DaysRange(val seed: String) {

    var stringFormat = ""

    private fun getTomorrow(dayIndex: Int): Int {
        if (dayIndex != 6) return dayIndex + 1
        return 0
    }

    override fun toString(): String =stringFormat


    init {
        if (isValidInput(seed)) {
            val dayFactory = DaysFactory()
            val indexes = seed.split(",").map { it.toInt() }
            val days = indexes.map { dayFactory.dayFromInt(it) }
            val ranges = splitIndexesToRanges(indexes)
            ranges.forEach { range ->
                if (range.size > 2) {
                    stringFormat += "${dayFactory.dayFromInt(range.first())} to ${dayFactory.dayFromInt(range.last())},"
                } else
                    range.forEach {
                        stringFormat += "${dayFactory.dayFromInt(it)},"
                    }
            }
            stringFormat = stringFormat.dropLast(1)
        }
    }

    private fun splitIndexesToRanges(daysRange: List<Int>): ArrayList<List<Int>> {
        val result = ArrayList<List<Int>>()
        val slicePoint = ArrayList<Int>()
        for (i in 0 until daysRange.size - 1) {
            if (getTomorrow(daysRange[i]) != daysRange[i + 1]) {
                slicePoint.add(i)
            }
        }

        var start = 0
        slicePoint.forEach {
            result.add(daysRange.slice(start..it))
            start = it + 1
        }
        result.add(daysRange.slice(start until daysRange.size))
        return result

    }

}

private fun isValidInput(seed: String): Boolean = true


fun main(args: Array<String>) {

    val input = listOf(
        "0,1,2,4,5,6",
        "5,6,0",
        "1,2,3,4"
    )

    input.forEach {
        val dr = DaysRange(it)
        println(dr)
    }
}

example output:

Sunday to Tuesday,Thursday to Saturday

Friday to Sunday

Monday to Thursday

1
votes

If you can, I would just stick to a given time API (e.g. java.time if you are on Java 8 or joda-time, etc.). The following solution would also work with your enum, but you need to adapt it a bit (i.e. DayOfWeek has getDisplayName and also allows to add single days and always get the next consecutive day).

I split the work into 3 separate tasks.

  1. reading the input into a list of DayOfWeek:

    fun readInput(input : String) : List<DayOfWeek> = input.splitToSequence(",")
        .map(String::toInt)
        .map {
          /* your 0 is Sunday which is 7 for DayOfWeek; rest is the same */
          if (it == 0) 7 else it
        }
        .map(DayOfWeek::of)
        .toList()
    

    Maybe you want to add .distinct().sorted() to it or want to validate the input beforehand... That depends on what you really want to ensure...

  2. transforming the day of weeks into a list of consecutive days:

    fun List<DayOfWeek>.toDayRangeList() : List<DayRange> = fold(mutableListOf<DayRange>()) { consecutiveDaysList, day ->
      consecutiveDaysList.apply {
        lastOrNull()?.takeIf { it.to + 1 == day }?.apply {
          to = day
        } ?: add(DayRange(day))
      }
    }
    

    For this I also introduced a DateRange-class in order to easily mutate the ending date... You can do this also with immutable objects, but I found this way easier. The DateRange also includes some helper methods to easily get the actual date in the form you want (in my example FULL_STANDALONE):

    data class DayRange(var from: DayOfWeek, var to: DayOfWeek = from) {
      private fun DayOfWeek.toFullString(locale : Locale) = getDisplayName(TextStyle.FULL_STANDALONE, locale)
      fun toString(locale : Locale) : String = when (from) {
        // TODO add missing locale specific strings!
        to -> from.toFullString(locale)
        to + 1 -> "All day"
        else -> "${from.toFullString(locale)} to ${to.toFullString(locale)}"
      }
      // just for convenience we use our custom toString-function:
      override fun toString() = toString(Locale.getDefault())
    }
    
  3. optionally "flatten" the list, i.e. if the last day and the first are consecutive, merge them into a single range. As we are dealing with DayOfWeek directly we can simply just add another day and compare two days, regardless whether one is the last day of the week or not:

    fun List<DayRange>.flatten(): List<DayRange> {
      if (size > 1) {
        val first = first()
        val last = last()
        if (last.to + 1 == first.from)
          return dropLast(1).drop(1)
              .toMutableList()
              .apply {
                add(DayRange(last.from, first.to))
              }
      }
      return this
    }
    
  4. Putting it all together / demo:

    listOf("1", "1,2", "1,0", "1,2,3", "1,2,4,5", "1,2,4,5,0", "1,2,3,4,5,6,0", "2,3,4,5,6,0,1")
          .forEach { input ->
            print(input)
            readInput(input)
                .toDayRangeList()
                .flatten()
                .joinToString(", ")
                .also {
                  println("-> $it")
                }
          }
    

    which prints the following:

    1 -> Monday
    1,2 -> Monday to Tuesday
    1,0 -> Sunday to Monday
    1,2,3 -> Monday to Wednesday
    1,2,4,5 -> Monday to Tuesday, Thursday to Friday
    1,2,4,5,0 -> Thursday to Friday, Sunday to Tuesday
    1,2,3,4,5,6,0 -> All day
    2,3,4,5,6,0,1 -> All day