Develop!/Kotlin

고차함수, 리스트 조작 in Kotlin

체리필터 2023. 10. 4. 17:16
728x90
반응형

함수를 다른 함수의 인자로 넘기거나, 함수가 반환값으로 함수를 돌려줄 수 있으면 언어가 고차함수를 지원한다고 말할 수 있다.

우선 람다를 저장한 변수의 예제를 살펴보면 아래와 같다.

fun isPlus() {
    val isPlus: (Int) -> Boolean = { it > 0 }
    val result = listOf(1, 2, -3).any(isPlus)
    println(result)
}

다음과 같은 방법으로 사용할 수 있다.

(파라미터타입1, ..., 파라미터타입N) -> 반환타입

위에서는 Int 타입 1개가 넘어가고 Boolean 타입으로 반환 하는 것으로 정의 하였고, "1, 2, -3"의 값이 하나씩 들어가게 되면 0보다 큰지를 판단해서 boolean으로 응답하는 함수를 정의한 후 isPlus에 담은 것이다. 결과는 아래와 같다.

참조를 통해 함수를 호출하는 구문은 일반 함수를 호출하는 구문과 똑같다. 우리가 많이 쓰는 예제를 참조를 통해 사용하는 코드를 보면 이해가 더 쉽다.

fun callingReference() {
    val helloWorld: () -> String = { "Hello World!" }
    val sum: (Int, Int) -> Int = { x, y -> x + y }

    println(helloWorld())
    println(sum(1, 2))
}

helloWorld는 파라미터가 없고 "Hello World!"를 리턴하는 함수이다. 호출할 때는 그냥 인자값 없는 "helloWorld()"를 호출하면 된다.

sum의 경우에는 2개의 Int 파라미터가 있기 때문에 호출할 때 "sum(1, 2)"와 같이 호출하면 된다.

List를 조작하는 함수 중에 대표적인 것으로 묶기(zip)와 평평하게 하기(flat) 기능이 있다. 먼저 zip 기능을 보자.

fun zipper() {
    val left = listOf("a", "b", "c", "d")
    val right = listOf("q", "r", "s", "t")

    val result1 = left.zip(right)
    println(result1)

    val result2 = left.zip(0..4)
    println(result2)

    val result3 = (10..100).zip(right)
    println(result3)
}

결과는 다음과 같다.

a, b, c, d를 가진 left와 q, r, s, t를 가진 right를 합치면 Pair 4개를 가진 List가 만들어진다.

0~4까지 가진 범위와 묶어도 똑같이 동작한다.

범위가 넓은 10~100을 주게 될 경우, 범위가 짧은 쪽을 따라 가게 된다. 한 쪽의 범위가 끝나면 zip 도 끝나게 된다.

zip을 이용해 나오는 Pair를 통해 객체를 생성하는 간단한 팁을 사용해 볼 수도 있다.

data class Person(
    val name: String,
    val Id: Int
)

fun zipAndTransform() {
    val names = listOf("Bob", "Jill", "Jim")
    val ids = listOf(1731, 9274, 8378)
    val persons = names.zip(ids) {
        name, id -> Person(name, id)
    }

    println(persons)
}

name과 id를 생성자 파라미터로 받는 Person class를 만든 다음, names, ids 두 가지를 zip으로 묶어서 Person List를 만들 수 있다.

결과는 아래와 같다.

하나의 List 안에서 인접한 원소와 묶으려면 zipWithNext()를 사용하면 된다. 간단한 사용법은 아래와 같다.

fun zipWithNext() {
    val list = listOf('a', 'b', 'c', 'd')
    val result1 = list.zipWithNext()
    println(result1)

    val result2 = list.zipWithNext { a, b -> "$a$b"}
    println(result2)
}

결과를 보면 어떤 식으로 묶이는지 알 수 있다.

다음으로 살펴볼 함수는 평평하게 만들 수 있는 flatten()이다. 예제를 확인해 보자.

fun flatten() {
    val list = listOf(
        listOf(1, 2),
        listOf(4, 5),
        listOf(7, 8)
    )
    println(list)
    println(list.flatten())
}

list 안의 list를 만들어 두고 flatten을 하기 전과 후를 비교해 보면 어떻게 동작하는지를 바로 알 수 있다.

모든 원소를 depth 없이 1차원적으로 펴 주는 모습을 볼 수 있다.

평평하게 펴 주는 다른 함수로 flatMap이 있다. flatten과 다른 점은 무엇인지 코드를 통해 알 수 있다.

fun flatMap() {
    val intRange = 1..3
    val result = intRange.map { a ->
        intRange.map { b -> a to b }
    }
    println(result)

    val result2 = result.flatten()
    println(result2)

    val result3 = intRange.flatMap { a ->
        intRange.map { b -> a to b }
    }
    println(result3)
}

intRange로 Pair 쌍을 만드는 map을 실행한 다음 flatten으로 평평하게 만드는 일을 하고 있다. 이 처럼 두 단계 (map, flatten)를 한 단계로 간편하게 할 수 있는 것이 flatMap이다.

result3 처럼 사용하면 복잡하게 두 단계에 거쳐 작업을 할 필요가 없어진다.

비슷한 듯 다른 다음의 예제를 통해서도 flatMap의 동작을 확인해 볼 수 있다.

class Book(
    val title: String,
    val authors: List<String>
)

fun whyFlatMap() {
    val books = listOf(
        Book("1984", listOf("George Orwell")),
        Book("Ulysses", listOf("James Joyce"))
    )

    val result1 = books.map { it.authors }.flatten()
    val result2 = books.flatMap { it.authors }

    println(result1)
    println(result2)
}

map, flatten으로 두 번에 나눠 해야 할 작업을 flatMap으로 한 번에 하는 것을 볼 수 있다.

 

728x90
반응형

'Develop! > Kotlin' 카테고리의 다른 글

멤버참조 in Kotlin  (0) 2023.09.27
컬렉션에 대한 연산 in Kotlin  (0) 2023.09.25
람다 in Kotlin  (0) 2023.09.25
제네릭스 in Kotlin  (0) 2023.09.22
안전한 호출과 엘비스 연산자 in kotlin  (0) 2023.09.14