From 433eae1c69926f9585664a990ad4a201cff9adbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=95=B8=EB=AA=A8?= Date: Sun, 10 Mar 2024 10:07:17 +0900 Subject: [PATCH] =?UTF-8?q?onCall=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/oncall/Application.kt | 7 ++- .../oncall/controller/OncallController.kt | 18 +++++++ .../src/main/kotlin/oncall/entity/Order.kt | 41 ++++++++++++++ .../oncall/exception/OncallException.kt | 15 ++++++ .../main/kotlin/oncall/model/OncallDate.kt | 53 +++++++++++++++++++ .../kotlin/oncall/service/OncallService.kt | 36 +++++++++++++ .../src/main/kotlin/oncall/view/InputView.kt | 51 ++++++++++++++++++ .../src/main/kotlin/oncall/view/OutputView.kt | 7 +++ 8 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 kotlin-oncall/src/main/kotlin/oncall/controller/OncallController.kt create mode 100644 kotlin-oncall/src/main/kotlin/oncall/entity/Order.kt create mode 100644 kotlin-oncall/src/main/kotlin/oncall/exception/OncallException.kt create mode 100644 kotlin-oncall/src/main/kotlin/oncall/model/OncallDate.kt create mode 100644 kotlin-oncall/src/main/kotlin/oncall/service/OncallService.kt create mode 100644 kotlin-oncall/src/main/kotlin/oncall/view/InputView.kt create mode 100644 kotlin-oncall/src/main/kotlin/oncall/view/OutputView.kt diff --git a/kotlin-oncall/src/main/kotlin/oncall/Application.kt b/kotlin-oncall/src/main/kotlin/oncall/Application.kt index 28436f5..123f3e7 100644 --- a/kotlin-oncall/src/main/kotlin/oncall/Application.kt +++ b/kotlin-oncall/src/main/kotlin/oncall/Application.kt @@ -1,5 +1,10 @@ package oncall +import oncall.controller.OncallController +import oncall.service.OncallService +import oncall.view.InputView +import oncall.view.OutputView + fun main() { - TODO("프로그램 구현") + OncallController.run(InputView, OutputView, OncallService) } diff --git a/kotlin-oncall/src/main/kotlin/oncall/controller/OncallController.kt b/kotlin-oncall/src/main/kotlin/oncall/controller/OncallController.kt new file mode 100644 index 0000000..dca2513 --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/controller/OncallController.kt @@ -0,0 +1,18 @@ +package oncall.controller + +import oncall.entity.Order +import oncall.service.OncallService +import oncall.view.InputView +import oncall.view.OutputView + +object OncallController { + + fun run(inputView: InputView, outputView: OutputView, oncallService: OncallService) { + val oncallDate = inputView.getOncallDate() + val (weekdayOrder: Order, holidayOrder: Order) = inputView.getOrder() + val oncallTable = oncallService.getOncallTable( + oncallDate, weekdayOrder = weekdayOrder, holidayOrder = holidayOrder + ) + outputView.printOncallTable(oncallTable) + } +} diff --git a/kotlin-oncall/src/main/kotlin/oncall/entity/Order.kt b/kotlin-oncall/src/main/kotlin/oncall/entity/Order.kt new file mode 100644 index 0000000..f7c8e1a --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/entity/Order.kt @@ -0,0 +1,41 @@ +package oncall.entity + +import oncall.exception.requireOncall + +const val MAX_NICKNAME_SIZE = 5 +const val MIN_ORDER_MEMBER = 5 +const val MAX_ORDER_MEMBER = 35 + +class Order(private val order: List) { + init { + requireOncall(order.all { it.length <= MAX_NICKNAME_SIZE }) { "닉네임은 최대 ${MAX_NICKNAME_SIZE}자 여야 합니다." } + requireOncall(order.size == order.distinct().size) { "중복된 이름이 있습니다." } + requireOncall(order.size in MIN_ORDER_MEMBER..MAX_ORDER_MEMBER) { "비상 근무자는 ${MIN_ORDER_MEMBER}명 이상 ${MAX_ORDER_MEMBER}명 이하여야 합니다. 비상 근무자 수 : ${order.size}" } + } + + private val size = order.size + private var index = 0 + private var queueMember: String? = null + + fun getNextTurnMember(previousMember: String?) = + if (isQueueMemberTurn(previousMember)) { + val nextTurnMember = queueMember + queueMember = null + nextTurnMember + } else { + getNextTurnMember(previousMember, this.getNextMember()) + } + + private fun getNextTurnMember(previousMember: String?, nextMember: String) = + if (previousMember == nextMember) { + // 만약 다음 멤버와 바로 전 멤버가 같다면 큐에 넣고 그 다음 멤버를 순서로 올린다. + queueMember = nextMember + this.getNextMember() + } else { + nextMember + } + + private fun isQueueMemberTurn(previousMember: String?) = queueMember != null && queueMember != previousMember + + private fun getNextMember() = this.order[(index++) % this.size] +} diff --git a/kotlin-oncall/src/main/kotlin/oncall/exception/OncallException.kt b/kotlin-oncall/src/main/kotlin/oncall/exception/OncallException.kt new file mode 100644 index 0000000..f3b1680 --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/exception/OncallException.kt @@ -0,0 +1,15 @@ +package oncall.exception + +const val ERROR_PREFIX = "[ERROR] " + +inline fun requireOncall(value: Boolean, lazyMessage: () -> Any) { + if (!value) { + throw OncallException(lazyMessage().toString()) + } +} + +class OncallException(message: String) : IllegalArgumentException() { + override val message = ERROR_PREFIX + message +} + +fun printlnOncallException(message: String) = println(ERROR_PREFIX + message) diff --git a/kotlin-oncall/src/main/kotlin/oncall/model/OncallDate.kt b/kotlin-oncall/src/main/kotlin/oncall/model/OncallDate.kt new file mode 100644 index 0000000..ef97892 --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/model/OncallDate.kt @@ -0,0 +1,53 @@ +package oncall.model + +import java.time.DayOfWeek +import java.time.DayOfWeek.* +import java.time.Month + +class OncallDate(val month: Month, private val startDayOfWeek: DayOfWeek) { + val monthDay = month.minLength() // 2월은 항상 28일 + + private val holidayMap = getHolidayMap(startDayOfWeek) + + private fun getHolidayMap(startDayOfWeek: DayOfWeek): Map> { + val holidayMap = mutableMapOf>() + for (day in 1..this.monthDay) { + val dayOfWeek = startDayOfWeek.plus((day - 1).toLong()) + holidayMap[day] = Pair(dayOfWeek, isHoliday(dayOfWeek, day)) + } + return holidayMap.toMap() + } + + private fun isHoliday(dayOfWeek: DayOfWeek, day: Int) = when (dayOfWeek) { + MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> + if (SPECIAL_HOLIDAY.contains(Pair(month.value, day))) { + DayType.WEEKDAY_HOLIDAY + } else { + DayType.WEEKDAY + } + + SATURDAY, SUNDAY -> DayType.HOLIDAY + } + + operator fun get(day: Int) = holidayMap[day] + + companion object { + val SPECIAL_HOLIDAY = listOf( + Pair(1, 1), + Pair(3, 1), + Pair(5, 5), + Pair(6, 6), + Pair(8, 15), + Pair(10, 3), + Pair(10, 9), + Pair(12, 25) + ) + } +} + +enum class DayType { + WEEKDAY, + WEEKDAY_HOLIDAY, + HOLIDAY, + ; +} diff --git a/kotlin-oncall/src/main/kotlin/oncall/service/OncallService.kt b/kotlin-oncall/src/main/kotlin/oncall/service/OncallService.kt new file mode 100644 index 0000000..6f6fe62 --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/service/OncallService.kt @@ -0,0 +1,36 @@ +package oncall.service + +import oncall.entity.Order +import oncall.exception.OncallException +import oncall.model.DayType +import oncall.model.OncallDate +import java.time.DayOfWeek + +object OncallService { + fun getOncallTable(oncallDate: OncallDate, weekdayOrder: Order, holidayOrder: Order): String { + var previousMember: String? = null + + return buildString { + for (day in 1..oncallDate.monthDay) { + val (dayOfWeek, dayType) = oncallDate[day] ?: throw OncallException("존재하지 않는 날짜입니다: ${day}일") + val nextMember = when (dayType) { + DayType.WEEKDAY -> weekdayOrder.getNextTurnMember(previousMember) + DayType.WEEKDAY_HOLIDAY, DayType.HOLIDAY -> holidayOrder.getNextTurnMember(previousMember) + } + previousMember = nextMember + append("${oncallDate.month.value}월 ${day}일 ${dayOfWeek.toKorean()}${if (dayType == DayType.WEEKDAY_HOLIDAY) "(휴일)" else ""} ${nextMember}\n") + } + } + } +} + +private fun DayOfWeek.toKorean() = when (this) { + DayOfWeek.MONDAY -> "월" + DayOfWeek.TUESDAY -> "화" + DayOfWeek.WEDNESDAY -> "수" + DayOfWeek.THURSDAY -> "목" + DayOfWeek.FRIDAY -> "금" + DayOfWeek.SATURDAY -> "토" + DayOfWeek.SUNDAY -> "일" + else -> throw IllegalArgumentException() +} diff --git a/kotlin-oncall/src/main/kotlin/oncall/view/InputView.kt b/kotlin-oncall/src/main/kotlin/oncall/view/InputView.kt new file mode 100644 index 0000000..6229b7c --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/view/InputView.kt @@ -0,0 +1,51 @@ +package oncall.view + +import camp.nextstep.edu.missionutils.Console +import oncall.entity.Order +import oncall.exception.printlnOncallException +import oncall.model.OncallDate +import java.time.DayOfWeek +import java.time.Month + +object InputView { + fun getOncallDate() = tryGetUserInput { + print("비상 근무를 배정할 월과 시작 요일을 입력하세요> ") + Console.readLine().toOncallDate() + } + + fun getOrder() = tryGetUserInput { + print("평일 비상 근무 순번대로 사원 닉네임을 입력하세요> ") + val weekdayOrder = Console.readLine().toOrder() + print("휴일 비상 근무 순번대로 사원 닉네임을 입력하세요>") + val holidayOrder = Console.readLine().toOrder() + Pair(weekdayOrder, holidayOrder) + } +} + +private fun String.toOncallDate(): OncallDate { + val (monthString, startDayOfWeekString) = this.split(",") + return OncallDate(Month.of(monthString.toInt()), startDayOfWeekString.toDayOfWeek()) +} + +private fun String.toDayOfWeek() = when (this) { + "월" -> DayOfWeek.MONDAY + "화" -> DayOfWeek.TUESDAY + "수" -> DayOfWeek.WEDNESDAY + "목" -> DayOfWeek.THURSDAY + "금" -> DayOfWeek.FRIDAY + "토" -> DayOfWeek.SATURDAY + "일" -> DayOfWeek.SUNDAY + else -> throw IllegalArgumentException() +} + +private fun String.toOrder() = Order(this.split(",")) + +private fun tryGetUserInput(inputFunction: () -> T): T { + while (true) { + try { + return inputFunction() + } catch (ignore: Exception) { + printlnOncallException("유효하지 않은 입력 값입니다. 다시 입력해 주세요.") + } + } +} diff --git a/kotlin-oncall/src/main/kotlin/oncall/view/OutputView.kt b/kotlin-oncall/src/main/kotlin/oncall/view/OutputView.kt new file mode 100644 index 0000000..cc53870 --- /dev/null +++ b/kotlin-oncall/src/main/kotlin/oncall/view/OutputView.kt @@ -0,0 +1,7 @@ +package oncall.view + +object OutputView { + fun printOncallTable(oncallTable: String) { + println(oncallTable) + } +}