함수를 다른 함수의 인자로 넘기거나, 함수가 반환값으로 함수를 돌려줄 수 있으면 언어가 고차함수를 지원한다고 말할 수 있다.
우선 람다를 저장한 변수의 예제를 살펴보면 아래와 같다.
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으로 한 번에 하는 것을 볼 수 있다.
'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 |