일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Python
- GitHub
- AWS
- TEST
- android
- relay
- 쿠링
- 코루틴
- Hilt
- Gradle
- 백준
- livedata
- boj
- 암호학
- androidStudio
- Kotlin
- ProGuard
- Rxjava
- Coroutine
- 코드포스
- MyVoca
- textfield
- Coroutines
- Compose
- Codeforces
- pandas
- 프로그래머스
- activity
- architecture
- MiTweet
- Today
- Total
이동식 저장소
[Kotlin] Sequence 본문
Sequence
Sequence
는 독특한 형태의 container이다. 기본적인 형태는 List
등의 Iterable
과 같지만, 세부적인 동작 방법이 다르다.
Iterable
에 map()
, filter()
, take()
을 적용한다고 해 보자. 우선 원본의 모든 원소에 map()
이 적용되고, 그 결과에 filter()
가 적용되고, 마지막으로 take()
가 적용된다. 즉 코드에서 함수를 적용한 순서대로 중간 결과
가 반환된다.
Sequence
에 같은 함수를 적용한다고 해 보자. Sequence
는 Iterable
과는 다르게 각 원소마다 모든 함수를 적용한다. 각 원소마다 map()
, filter()
, take()
을 모두 적용해 본다는 뜻이다. 만약 원소가 filter()
의 조건에 맞지 않는다면 filter()
까지만 함수가 적용되고 뒤에 있는 take()
은 적용되지 않는다. 차이가 느껴지는가?
Sequence
의 또다른 특징으로는, Sequence
의 연산이 최대한 늦게(lazily
) 적용된다는 것이다. Iterable
은 연산 함수를 호출한 즉시 결과가 반환된다. 그러나 Sequence
는 가능한 한 연산을 늦게 적용한다. 예컨대 for
문에서 연산 결과를 요청하기 전까지는 연산하지 않고, 그마저도 원소 하나씩만 적용한다.
이러한 특징으로 인해 Sequence
는 대량의 데이터에 연산을 적용할 때 더 효율적이다. 하지만 데이터가 작다면 lazy
성질로 인해 오버헤드가 생길 수 있으니 상황에 맞게 Sequence
와 Iterable
을 적절히 활용하자.
Sequence 생성
다른 컬렉션처럼 sequenceOf()
함수를 이용하여 Sequence
를 만들 수 있다.
val numbersSequence = sequenceOf("four", "three", "two", "one")
또는 다른 Iterable
로부터 asSequence()
를 호출하여 만들 수도 있다.
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
또는 람다식으로 만들 수도 있다. generateSequence()
의 첫 번째 매개변수에는 초기값을, 두 번째 매개변수에는 다음 값을 계산할 람다식을 넘겨줘야 한다. Sequence
를 중간에 끝내고 싶을 경우 람다식에서 null
을 반환하도록 해야 한다. 그렇지 않은 경우에는 Sequence
가 무한히 이어질 수 있다.
val oddNumbers = generateSequence(1) { it + 2 } // it: 이전 값
println(oddNumbers.take(5).toList())
//println(oddNumbers.count()) // error: null이 없으므로 sequence가 무한히 이어진다.
[1, 3, 5, 7, 9]
또는 chunk
로부터 만들 수도 있다. chunk
란 yield()
또는 yieldAll()
을 말한다. yield()
는 원소 하나를 인자로 받고, yieldAll()
은 Iterable
또는 Sequence
객체를 인자로 받을 수 있다. yieldAll()
은 원소를 무한히 제공할 수 있으므로 웬만하면 가장 마지막에 부르는 게 좋다.
val oddNumbers = sequence {
yield(1)
yieldAll(listOf(3, 5))
yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())
[1, 3, 5, 7, 9]
Sequence 연산
Sequence
의 연산은 다음의 두 가지로 나눌 수 있다.
Stateless
:Sequence
의 상태가 거의 필요 없는 연산.map()
,filter()
,take()
등이 있다.Stateful
:Sequence
의 원소의 수 등 상태 정보가 많이 필요한 연산.
다음의 기준으로도 나눌 수 있다.
intermediate
:Sequence
연산이 다른Sequence
를 반환할 때.terminal
:Sequence
연산이Sequence
가 아닌 값을 반환할 때.toList()
,sum()
등이 있다.
일반적으로 Sequence
는 여러 번 반복될(iterated) 수 있으나, 특정 클래스의 경우 단 한 번만 반복될 수 있다. 레퍼런스 문서에 자세히 설명되어 있다.
Sequence 연산 예시
Iterable
과 Sequence
의 차이점을 살펴보자.
Iterable
여러 개의 단어 중 길이가 3 이상인 처음 4개의 단어를 찾고자 한다.
val words = "The quick brown fox jumps over the lazy dog".split(" ")
val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
.map { println("length: ${it.length}"); it.length }
.take(4)
println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)
filter: The
filter: quick
filter: brown
filter: fox
filter: jumps
filter: over
filter: the
filter: lazy
filter: dog
length: 5
length: 5
length: 5
length: 4
length: 4
Lengths of first 4 words longer than 3 chars:
[5, 5, 5, 4]
실행 결과를 보면, filter()
가 모두 적용된 다음 map()
이 적용되고, 그 후에 take()
가 적용되었다. 코드에 적힌 순서대로 연산이 수행되었다.
Sequence
Sequence
로 같은 연산을 수행해 보자.
val words = "The quick brown fox jumps over the lazy dog".split(" ")
// List를 Sequence로 변환
val wordsSequence = words.asSequence()
val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 }
.map { println("length: ${it.length}"); it.length }
.take(4)
println("Lengths of first 4 words longer than 3 chars")
// terminal operation: 결과를 List로 반환
println(lengthsSequence.toList())
Lengths of first 4 words longer than 3 chars
filter: The
filter: quick
length: 5
filter: brown
length: 5
filter: fox
filter: jumps
length: 5
filter: over
length: 4
[5, 5, 5, 4]
원소 하나하나마다 filter()
, map()
, take()
가 적용되고 있다. 4개의 원소를 가져온 후에는 연산이 중단된다. 이렇게 하면 Iterable
보다 적은 연산으로 결과를 얻을 수 있다.
앱이나 다른 프로그램에서 쏠쏠하게 써먹을 수 있을 것 같다.
'Primary > Kotlin' 카테고리의 다른 글
[Kotlin] Coroutines - Cancellation and Timeouts (0) | 2021.01.20 |
---|---|
[Kotlin] Coroutines - Basics (0) | 2021.01.19 |
[Kotlin] Thread 생성 및 실행 (0) | 2021.01.14 |
[Kotlin] Collections 확장 함수 (0) | 2021.01.13 |
[Kotlin] 데이터 클래스 (0) | 2021.01.08 |