오늘을 코틀린의 시퀀스(Sequence)를 알아볼까 합니다.
시퀀스 한국말로 해석하면 수열입니다. 수열은 수가 나열된 것을 말합니다. 코틀린에서도 이와 비슷하게 리스트처럼 수들을 가지고 있을 수 있습니다. 그렇다면 코틀린은 리스트도 있는데 왜 시퀀스를 만들었을까요. 그것은 시퀀스만의 장점이 있기 때문입니다.
시퀀스의 특징에 대한 설명에 앞서 하나의 코드를 보여주겠습니다.
//1번
val list = "it is a test code".split(" ")
val conversionList = list.filter { it.length > 1 }.map { "[$it]" }.take(2)
println(conversionList.joinToString(" "))//[it] [is]
println()
//2번
val sequence = "it is a test code".split(" ").asSequence()
val conversionSequence = sequence.filter { it.length > 1 }.map { "[$it]" }.take(2)
println(conversionSequence.joinToString(" "))//[it] [is]
//출력 결과
//it
//is
//a
//test
//code
//it
//is
//test
//code
//
//[it] [is]
//it
//it
//is
//is
//[it] [is]
conversionList와 conversionSequence의 값은 같지만 동작은 완전히 다릅니다. 출력 결과를 보다시피 컬렉션에서 스코프함수를 연속해서 사용한다면 컬렉션의 값을 순선대로 돌면서 스코프를 모두완수하고 다음 스코프의 동작을 합니다. 그렇기 때문에 작은 데이터들을 다룰 때는 편리하지만 규모가 큰 데이터들을 다룰 때는 시간이 오래 걸립니다.
시퀀스의 경우 스코프함수를 연속해서 사용한다면 시퀀스의 값 하나씩 스코프함수를 모두 수행하여 함수의 끝에 시퀀스로 저장됩니다. 이렇게 말하면 중간에 변환이 없기 때문에 무조건적으로 시퀀스가 좋아 보이지만 대부분의 사람들이 리스트를 사용하는 데에는 이유가 있습니다. 시퀀스의 경우 처리된 결과가 요구 됐을 때에는 실제연산이 이루어지면 느리게 처리됩니다. 그 대신 규모가 큰 데이터들을 다룬다면 변환이 없기 때문에 이러한 측면에서 보았을 때는 시퀀스가 좋아 보입니다.
시퀀스를 만드는 방법
시퀀스를 만드는 방법으로는 4가지 정도가 있습니다
//1. sequenceOf(varargs) 함수이용
val sequence = sequenceOf(1, 2, 3, 4, 5)
//2. asSequence()함수를 이용한 collection을 sequence로 변형
val numbers = listOf(1, 2, 3, 4, 5)
val sequence = numbers.asSequence()
//3. generateSequence(초기값){ 나열할 규칙정의 }
val sequence = generateSequence(1) { it + 1 }// 1,2,3,4,5,6 ...
//4. sequence() 함수, yield와 yieldAll을 이용하여 다음값을 지정해주면 됩니다.
val sequence = sequence {
yield(1)
yieldAll(listOf(2, 3))
yieldAll(generateSequence(4) { it + 1 })
}// 1,2,3,4,5,6 ...
1번과 2번 같은 경우 size가 정해져 있어 count를 할 경우 넣어준 값만큼 나오지만 3,4번 같은 경우 범위가 정해지지 않아 여기다 count를 한경우 java.lang.ArithmeticException: Count overflow has happened. 같이 오버플로우에러가 발생하기 때문에 사용하려고 한다면 take() 함수난 toList() 함수를 같이 사용해야 합니다.
val primes: Sequence<Int> = sequence {
var numbers = generateSequence(2) { it + 1 }
while (true) {
val prime = numbers.first()
yield(prime)
numbers = numbers.drop(1).filter { it % prime != 0 }
}
}
print(primes.take(10).toList()) // [2,3,5,7,11,13,17,19,23,29]
시퀀스를 응용하여 소수들을 구하는 시퀀스를 만들어보았습니다. 위에서 배웠듯 시퀀스는 비동기적으로 실행되기 때문에 이처럼 시퀀스의 필터링을 계속하여 소수들을 받아올 수 있게 되는 것입니다.
그렇지만 이 코드에는 많은 단점이 있습니다. 그중 하나가 성능저하입니다. numbers 시퀀스에서 drop(1)과 filter를 사용하여 첫 번째 소수를 제외하고 나머지 숫자를 필터링합니다. 하지만 이러한 작업은 매번 반복할 때마다 수행되며, 중복되는 계산이 많이 발생합니다. 이는 성능 저하를 가져올 수 있습니다.
마치며
이처럼 시퀀스는 여러장점이있지만 그만큼 단점도 많은 자료형입니다. 그렇기 때문에 사용할 때는 한번 더 생각해서 과연 여기에 맞는 자료형인지 한번 더 생각해 봐야 될 것 같습니다.
참고
책 : Kotlin In Action
https://product.kyobobook.co.kr/detail/S000001804588
책 : 이비티브 코틀린
https://product.kyobobook.co.kr/detail/S000001033129
https://kotlinlang.org/docs/sequences.html
https://iosroid.tistory.com/79
'코틀린 > 문법및 라이브러리' 카테고리의 다른 글
[Kotlin] 여러 종류의 반복문 (0) | 2023.08.04 |
---|---|
[Kotlin] 교집합(intersect), 합집합(union), 차집합(subtract) (0) | 2023.07.31 |
[Kotlin] Result class 가 뭐지? (0) | 2023.07.26 |
[Kotlin] Contract 로 스마트 케스팅 (0) | 2023.07.18 |
[Kotlin] 콜렉션(Collection) 파보기 (0) | 2023.07.12 |