본문 바로가기

코틀린

코틀린: 3장 테스트에 도전해 보세요 (4번 정답 해설)

문제 4 두 숫자의 최소 공배수(LCM, least common multiples)를 구하는 함수 findLCM()을 람다 식으로 바꿔서 코드를 작성해 보세요.

■ 최소 공배수(LCM)와 최대 공약수 (GCD, greatest common divisor)
3과 7의 최소 공배수는 21이지만, 12와 18의 최소 공배수는 36입니다. 최소공배수 = (a × b) / 최대공약수 입니다. 최소 공배수는 최대 공약수(GCD, greatest common divisor)와 반비례 관계입니다.  3과 7의 최대 공약수는 1이지만 최소 공배수는 21입니다.  12와 18의 최대 공약수는 6이지며, 최소 공배수는 36 입니다(인터넷 검색을 해 보면, 대부분 위 공식을 구현한 코드를 제시할 겁니다. 최소 공배수 코드는 마트에서 파는 상품이 아닙니다.).

책(p.121)에서 소개한 코드는 조금 다르죠! (혹시 마트에서 파는 상품이 아니라 실망했나요?) 코드를 자세히 들여다 볼까요. 먼저 두 수 a와 b 중 큰 수(max)를 찾고, 이 값을 lcm의 초깃값으로 할당합니다. while 루프에서 lcm이 a와 b의 배수인지 조사합니다. lcm이 a 와 b의 배수가 아니면 lcm 을 max 만큼 증가시킵니다. 재미있는 점은 lcm은 a와 b 중 큰 값을 계속 더해가는 점입니다. 두 수가 9와 17일 때, 17은 9번 더하면 최소 공배수를 구하지만, 9는 17번을 더해야합니다. 

fun findLCM(a: Int, b: Int): Int {
    val max = if (a > b) a else b
    var lcm = max
    while (true) {
        if (lcm % a == 0 && lcm % b == 0) {
            break
        }
        lcm += max
    }
    return lcm
}

물론 while 문에서  조건식은 논리 AND가 아니라 논리 OR 를 사용할 수 있습니다. 거의 대부분 프로그래밍 언어에서 논리 AND는 첫 번째 조건이 false이면 두 번째 조건은 검사하지 않습니다. 마찬가지로 논리 OR는 첫 번째 조건이 true이면 두 번째 조건을 검사하지 않습니다. 프로그래밍 언어의 논리 연산식에서 첫 번째 조건이 더 중요합니다.

while (lcm % a > 0 || lcm % b > 0) {
    lcm += max
}

 

■ 정답 : 일반 함수를 람다 식으로 변환
함수를 람다 식으로 변환하는 방법은 기계적(?)입니다(한 번 방법을 익히는 걸로 충분하다는 뜻입니다). 함수 findLCM()을 람다 식으로 변환하려면, 함수 타입을 참조하는 변수(findLCMbyLambda)부터 선언해야 합니다. 함수 findLCM()은 2개의 Int 타입 인자를 전달받아 Int 타입을 반환하므로, 여기에 맞게 함수 타입( (Int, Int) -> Int )을 선언합니다. 람다 식 블록에서는 먼저 형식 인자 이름을 정의하는 것이 필요합니다. 마지막 문장에 반환할 값(lcm)을 지정해야 합니다. 나머지 코드는 함수 블록에 있던 내용을 고스란이 옮기면 됩니다. 

fun findLCM(a: Int, b: Int): Int {
    val max = if (a > b) a else b
    var lcm = max
    . . . . .
    return lcm
}
    
val findLCMbyLambda: (Int, Int) -> Int = { a, b -> // 형식 인자 선언
    val max = if (a > b) a else b
    var lcm = max
    . . . . .
    lcm  // 값을 반환
}

 

함수 타입을 생략하려면 람다 식 블록의 형식 인자에 반드시 타입을 선언해야 합니다.

val findLCMbyLambda = { a: Int, b: Int ->
    . . . . . 
}

 

전체 코드를 볼까요. val로 선언한 변수 findLCMbyLambda는 지역 변수로 선언했지만, 함수 바깥에 전역 변수로 선언해도 됩니다.

fun main() {
    val n1 = 9
    val n2 = 17
    val findLCMbyLambda: (Int, Int) -> Int = { a, b ->
        val max = if (a > b) a else b
        var lcm = max
        while (lcm % a > 0 || lcm % b > 0) {
            lcm += max
        }
        lcm
    }

    println("The LCM of $n1 and $n2 = ${findLCMbyLambda(n1, n2)}")   // 153
}

 

■ practice : 최대 공약수(GCD)를 사용해 최소 공배수(LCM)을 구하는 방법
문제와는 상관없지만, 앞에서 설명했던 최대 공약수와 최소 공배수의 관계를 람다 식을 사용해 구현했습니다. 람다 식 참조 변수 gcd는 함수 밖에 전역 변수(top-level variable)로 정의했습니다. findLCMbyLambda는 앞의 코드처럼 지역 변수로 정의했습니다(코드 복잡성을 줄이기 위해 두 수가 모두 0이거나 음수인지 검사하는 코드는 포함하지 않았습니다).

val gcd = { a: Int, b: Int ->
    var x = a
    var y = b
    while (y != 0) {
        val temp = y; y = x % y; x = temp
    }
    x
}

fun main() {
    val n1 = 9
    val n2 = 17

    val findLCMbyLambda = { a: Int, b: Int ->
        (a * b) / gcd(a, b)
    }
    println("The LCM of $n1 and $n2 = ${findLCMbyLambda(n1, n2)}")   // 153
}