코틀린 입문 스터디 (9) 실습 : Mastermind in a functional style, Nice String, Taxi Park

mook2_y2
10 min readMar 1, 2019

--

스터디파이 코틀린 입문 스터디 (https://studypie.co/ko/course/kotlin_beginner) 관련 자료입니다.

코틀린 입문반은 Kotlin을 직접 개발한 개발자가 진행하는 Coursera 강좌인 “Kotlin for Java Developers” (https://www.coursera.org/learn/kotlin-for-java-developers) 를 기반으로 진행되며 아래는 본 강좌 요약 및 관련 추가 자료 정리입니다.

목차

(1) Introduction

(2) From Java to Kotlin

(3) Basics

(4) Control Structures

(5) Extensions

(6) 실습 : Mastermind game

(7) Nullability

(8) Functional Programming

(9) 실습 : Mastermind in a functional style, Nice String, Taxi Park

(10) Properties

(11) Object-oriented Programming

(12) Conventions

(13) 실습 : Rationals, Board

(14) Inline functions

(15) Sequences

(16) Lambda with Receiver

(17) Types

(18) 실습 : Game 2048 & Game of Fifteen

1. Matermind in a functional style

문제 설명

예시 답안 및 설명

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
}

--

--