728x90
반응형
✅ 지연 계산(lazy) 컬렉션 연산
[Kotlin In Action] 5장. 람다로 프로그래밍 - 컬렉션 함수형 API
✅ 컬렉션 함수형 API 함수형 프로그래밍 스타일을 사용하면 컬렉션을 다룰 때 편리함 대부분의 작업에 라이브러리 함수를 활용할 수 있으며 그로 인해 코드를 간결하게 작성할 수 있음 📌 필수
dahoonkk.tistory.com
- 이전 장에서 map이나 filter와 같은 몇 가지 컬렉션 함수를 살펴봤다.
- 이 함수들의 결과는 컬렉션을 즉시 생성한다.
- 이는 컬렉션 함수를 연쇄하면 매 단계마다 중간 결과를 새로운 컬렉션에 임시로 담는다는 뜻
people.map(Person::name).filter( it.startsWith("A") }
- 위의 코드는 연쇠 호출이 리스트를 2개 만든다는 뜻
- 한 리스트는 filter의 결과를 담고, 다른 하나는 map의 결과를 담는다.
- 원본 리스트에 원소가 2개밖에 없다면 리스트가 2개 더 생격도 큰 문제는 아니겠지만, 원소가 수백만 개가 되면 효율이 떨어지게 된다.
- 위의 문제를 효율적으로 만들기 위해서는 각 연산이 컬렉션을 직접 사용하는 대신 시퀀스를 사용하게 만들어야 한다.
- 시퀀스를 사용하면 임시 컬렉션을 사용하지 않고도 컬렉션 연산을 연쇄할 수 있다.
people.asSequence() // 원본 컬렉션을 시퀀스로 변환
.map(Person::name) // 시퀀스도 컬렉션과 똑같은
.filter { it.startsWith("A") } // API를 제공
.toList() // 결과 시퀀스를 다시 리스트로 변환
- 전체 연산을 수행한 결과는 이름이 A로 시작하는 사람의 목록으로 이전의 예제와 같지만, 위의 예제의 경우 중간 결과를 저장하는 컬렉션이 생기지 않기 때문에 원소가 많은 경우 성능이 더 좋아질 수 있다.
- 코틀린 지연 계산 시퀀스는 Sequence 인터페이스에서 시작하며 그 안에는 iterator라는 단 하나의 메소드만 있다.
- 해당 메소드를 통해 시퀀스로부터 원소 값을 얻을 수 있다.
- 또한 시퀀스를 생성한 후 컬렉션으로 변환하는 것이 좋다.
- 그 이유는 시퀀스의 원소를 차례로 이터레이션한다면 시퀀스를 직접 사용해도 되지만, 인덱스를 활용해 원소에 접근하는 등의 다른 API 메서드가 필요한 경우에는 리스트로 변환하여 사용한다.
📌 시퀀스 연산 실행 : 중간 연산과 최종 연산
- 시퀀스에 대한 연산은 중간 연산과 최종 연산으로 나뉜다.
❗ 중간 연산
- 다른 시퀀스를 반환한다.
- 결과는 최초 컬렉션에 대해 변환을 적용한 시퀀스로부터 일련의 계산을 수행해 얻을 수 있는 컬렉션이나 원소, 숫자 또는 객체다.
- 중간 연산은 항상 지연 계산된다.
listOf(1, 2, 3, 4).asSequence()
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 ==0}
- 이 코드를 실행하면 아무 내용도 출력되지 않는다.
- 이는 map과 filter 변환이 늦춰져 결과를 얻을 필요가 있을 때(최종 연산이 호출될 때) 적용된다.
❗ 최종 연산
- 최종 연산을 호출하면 연기됐던 모든 계산이 수행되며 결과를 반환한다.
listOf(1, 2, 3, 4).asSequence()
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 ==0}
.toList()
// map(1) filter(1) matp(2) filter(4) map(3) filter(9) map(4) filter(16)
- 위의 예제에서 연산 수행 순서를 잘 알아둬야 한다.
- 직접 연산을 구현할 경우 map 함수를 각 원소에 대해 먼저 수행해서 새 시퀀스를 얻고, 그 시퀀스에 대해 다시 filter를 수행할 것이다.(컬렉션에 대한 작동 방식)
- 하지만 시퀀스에 대한 map과 filter는 그렇지 않다.
- 시퀀스의 경우 모든 연산은 각 원소에 대해 순차적으로 적용된다.
- 즉, 첫 번째 원소가 처리되고, 다시 두 번째 원소가 처리되며, 이런 처리가 모든 원소에 대해 적용된다.
- 따라서, 원소에 연산을 차례대로 적용하다가 겨로가가 얻어지면 그 이후의 원소에 대해서는 변환이 이뤄지지 않을 수 있다.
- 다음 예제를 통해 컬렉션 사용에 대한 작동 방식(즉시 계산)과 시퀀스에 대한 작동 방식(지연 계산)을 비교해보자.
println(listOf(1, 2, 3, 4).map{it * it}.find{it>3})
// 4
println(listOf(1, 2, 3, 4).asSequecne().map {it * it}.find{it > 3})
// 4
- 위의 예제는 같은 4를 출력한다는 점은 같지만 모든 원소에 대한 값이 변화되는지 아닌지에 대한 차이를 보인다.
- 아래 그림과 같이 컬렉션을 사용하면 리스트가 다른 리스트로 변환된다.
- 따라서 map 연산은 3과 4를 포함해 모든 원소를 변환한다.
- 그 후 find가 술어를 만족하는 첫 번째 원소인 4를 찾는다.
- 시퀀스를 사용하면 find 호출이 원소를 하나씩 처리하기 시작한다.
- 최초 시퀀스로부터 수를 하나 가져와 map에 지정된 변환을 수행한 다음 find에 지정된 술어를 만족하는지 검사한다.
- 최초 시퀀스에서 2를 가져오면 제곱 값이 3보다 커지기 때문에 그 제곱 값을 겨로가로 반환한다.
- 이때, 이미 답을 찾았기 때문에 3과 4에 대한 처리를 진행하지 않는다.
728x90
반응형
'Studying > Kotlin' 카테고리의 다른 글
[Kotlin In Action] 5장. 람다로 프로그래밍(5) - 수신 객체 지정 람다 : with와 apply (0) | 2023.05.23 |
---|---|
[Kotlin In Action] 5장. 람다로 프로그래밍(4) - 자바 함수형 인터페이스 활용 (0) | 2023.05.23 |
[Kotlin In Action] 5장. 람다로 프로그래밍(2) - 컬렉션 함수형 API (0) | 2023.05.18 |
[Kotlin In Action] 5장. 람다로 프로그래밍(1) - 람다 식과 멤버 참조 (0) | 2023.05.17 |
[Kotlin In Action] 4장. 클래스, 객체, 인터페이스(4) - object 키워드 : 클래스 선언과 인스턴스 생성 (0) | 2023.05.16 |