코틀린 입문 스터디 (9) 실습 : Mastermind in a functional style, Nice String, Taxi Park
스터디파이 코틀린 입문 스터디 (https://studypie.co/ko/course/kotlin_beginner) 관련 자료입니다.
코틀린 입문반은 Kotlin을 직접 개발한 개발자가 진행하는 Coursera 강좌인 “Kotlin for Java Developers” (https://www.coursera.org/learn/kotlin-for-java-developers) 를 기반으로 진행되며 아래는 본 강좌 요약 및 관련 추가 자료 정리입니다.
목차
(9) 실습 : Mastermind in a functional style, Nice String, Taxi Park
1. Matermind in a functional style
문제 설명
- 앞서 풀었던 실습 : Mastermind game를 Functional Programming 파트에서 배운 Collection 연산 확장함수를 사용하여 다시 풀어보는 문제입니다.
예시 답안 및 설명
fun evaluateGuess(secret: String, guess: String): Evaluation {
val secretHashMap = secret.groupBy{ it }
val guessHashMap = guess.groupBy { it }
var stringPairs = guess.zip(secret)
val rightPosition = stringPairs.count{ it.first == it.second }
val wrongPosition = stringPairs.count{
it.first != it.second
&& it.first in secretHashMap.keys
&& secretHashMap[it.first] == guessHashMap[it.first]
}
return Evaluation(rightPosition, wrongPosition)
}
groupBy
를 사용하여 문자열에 포함된 각 알파벳을 key로 하고, 각 알파벳이 나온 횟수만큼의 길이를 가지는 Collection을 value로 하는 map을 생성합니다. (ex:{A=[A,A], B=[B], C=[C]}
)zip
을 사용하여 두 문자열을 순서대로 합친 Pair로 구성된 Collection을 생성합니다. (ex:[(A,A), (D,A), (F,B), (E,C)]
)count
를 사용하여 rightPosition과 wrongPosition을 판별하는 로직을 lambda 부분에 작성합니다.
2. Nice string
문제 설명
- 다음 3가지 조건 중 2가지 이상을 만족하는 문자열인지 여부를 판단하는 확장함수를 만드는 문제입니다.
- 1) bu, ba, be를 포함하지 않는다.
- 2) 모음 (a, e, i, o, u)을 최소 3개 이상 포함한다.
- 3) 같은 알파벳이 최소 2번 이상 연속으로 반복되어 나온다.
- Ex 1. “bac”는 ba를 포함하여 1) 조건을 만족하지 않고, 모음이 1번만 나와 2) 조건을 만족하지 않고, 3)도 만족하지 않으므로 Nice가 아닙니다.
- Ex 2. “aza”는 1) 조건은 만족하지만 모음이 2번만 나오므로 2) 조건을 만족하지 않고, 3) 조건도 만족하지 않으므로 Nice가 아닙니다.
- Ex 3. “abaca”는 모음이 3번 나오므로 2) 조건을 만족하지만 “ba”를 포함하여 1) 조건을 만족하지 않고 3) 조건도 만족하지 않으므로 Nice가 아닙니다.
- Ex 4. “baaa”는 “ba”를 포함하여 1) 조건을 만족하지 않지만, 모음이 3번 나와 2) 조건을 만족하며, a가 연속으로 반복되므로 3) 조건을 만족하여 Nice입니다.
- Ex 5. “aaab”는 모든 조건을 만족하므로 Nice입니다.
예시 답안 및 설명
fun String.isNice(): Boolean {
val charPairs = this.zip(this.substring(1))
val isFirstPassed = charPairs
.none { "${it.first}${it.second}".compareTo("bu") == 0
|| "${it.first}${it.second}".compareTo("ba") == 0
|| "${it.first}${it.second}".compareTo("be") == 0
}
val isSecondPassed = this
.count{
it == 'a' || it == 'e' || it == 'i' || it == 'o' || it =='u'
} >= 3
val isThirdPassed = charPairs
.any {it.first.compareTo(it.second) == 0}
val isNice = listOf(isFirstPassed,isSecondPassed,isThirdPassed)
.count{ it } >= 2
if(!isNice) return false
return true
}
this.zip(this.substring(1))
을 통해 문자열을 한칸씩 밀어 Pair를 만든 Collection을 생성합니다. (ex: “abaca” ->[(a, b), (b, a), (a, c), (c, a)]
) 이를 1)과 3) 조건 만족 여부 판단시 사용합니다.- “1) bu, ba, be를 포함하지 않는다.” 조건 만족 여부 판단을 위해
none
을 사용합니다. - “2) 모음 (a, e, i, o, u)을 최소 3개 이상 포함한다.” 조건 만족 여부 판단을 위해
count
를 사용합니다. - “3) 같은 알파벳이 최소 2번 이상 연속으로 반복되어 나온다.” 조건 만족 여부 판단을 위해
any
를 사용합니다. - 3가지 중 2가지 조건 이상이 만족하는지 여부 판단을 위해
count
를 사용합니다.
3. Taxi Park
문제 설명
- TaxiPark.kt 파일에 정의되어 있는
Driver
,Passenger
,Trip
은 각각 택시 운전사, 승객, 탑승기록 정보를 담고 있으며,TaxiPark
는 이 정보들을 모두 담고 있는 클래스입니다. - 이에 대해 아래 6가지 목표를 수행하는 각각의 함수를 구현하는 문제입니다.
- 1) 탑승기록이 한번도 없는 택시 운전사들을 찾는 함수입니다.
- 2) 주어진
minTrips
횟수 이상의 탑승기록이 있는 승객들을 찾는 함수입니다. - 3) 주어진
driver
택시 운전사에 대한 탑승기록이 최소한 1번 이상 있는 승객들을 찾는 함수입니다. - 4) 탑승기록 중 절반 이상에서 할인 혜택을 받은 승객 (smart passenger)들을 찾는 함수입니다.
- 5) 탑승기록들의 탑승시간 (duration)을 0~9(분), 10~19(분), 20~29(분) 등의 구간으로 볼 때 어떤 구간에 가장 많은 탑승기록이 있는지를 찾는 함수입니다.
- 6) 20% 미만의 택시 운전사가 전체 소득의 80% 이상을 받는지 여부를 확인하는 함수입니다.
예시 답안 및 설명
// Task #1
fun TaxiPark.findFakeDrivers(): Set<Driver> =
this.allDrivers.filter{ driver ->
driver !in this.trips.groupBy { trip ->
trip.driver
}.keys
}.toSet()
// Task #2
fun TaxiPark.findFaithfulPassengers(minTrips: Int): Set<Passenger> =
this.allPassengers.filter{ passenger ->
this.trips.flatMap {trip ->
trip.passengers
}.count{ trippedPassenger ->
trippedPassenger == passenger} >= minTrips
}.toSet()
// Task #3
fun TaxiPark.findFrequentPassengers(driver: Driver): Set<Passenger> =
this.trips.filter { trip ->
trip.driver == driver
}.flatMap {trip ->
trip.passengers
}.distinct().toSet()
// Task #4
fun TaxiPark.findSmartPassengers(): Set<Passenger> {
fun List<Trip>.getNumOfTrips():Map<Passenger, Int>{
return this.flatMap { trip ->
trip.passengers
}.groupBy {
it
}.map {
it.key to it.value.size
}.toMap()
}
val numOfDiscountedTrips = this.trips.filter { trip ->
trip.discount != 0.0
}.getNumOfTrips()
return this.trips.getNumOfTrips()
.filter { (passenger, numOfTrips) ->
numOfDiscountedTrips[passenger]!!.div(numOfTrips) >= 0.5
}.map {(passenger, _) ->
passenger
}.toSet()
}
// Task #5
fun TaxiPark.findTheMostFrequentTripDurationPeriod(): IntRange? {
return this.trips.groupBy { trip ->
(trip.duration / 10) * 10 .. (trip.duration / 10) * 10 + 9
}.maxBy { (_, tripsList) ->
tripsList.size
}?.key
}
// Task #6
fun TaxiPark.checkParetoPrinciple(): Boolean {
if (this.trips.isNullOrEmpty()) return false
val totalIncome = this.trips.sumByDouble { it.cost }
val numOfDrivers = this.allDrivers.count()
val paretoIncome = trips.groupBy { trip ->
trip.driver
}.map {(driver, tripsList) ->
driver to tripsList.sumByDouble { trip ->
trip.cost
}
}.sortedBy {(_, cost) -> cost
}.slice(0 .. (numOfDrivers * 0.2).toInt())
.sumByDouble { (_, cost) -> cost }
if (paretoIncome < totalIncome * 0.8) return false
return true
}