Ranges
범위 표현식은 연산자 형식인 ..
을 가지는 rangeTo
함수로 구성된다. in 이나 !in과 함게 쓰인다. 범위는 모든 비교가능한 타입에 대해 정의되지만, 정수형 기본 타입에 최적화된 구현을 제공한다.
if (i in 1..10) { // 1 <= i && i <= 10 와 동일
println(i)
}
정수 타입 범위 (IntRange
, LongRange
, CharRange
)에는 반복가능한 특수 기능을 제공한다. 컴파일러가 이를 자바의 인덱스 기반 for-loop 와 동일하게 변환하여 오버헤드가 없다.
for (i in 1..4) print(i) // "1234" 출력
for (i in 4..1) print(i) // 아무것도 출력하지 않음
숫자를 역순으로 반복하고 싶은 경우 표준 라이브러리에 있는 downTo()
함수를 사용하면 된다.
for (i in 4 downTo 1) print(i) // "4321" 출력
1씩 증가/감소가 아닌 임의의 단계롤 숫자를 반복하는 경우 step()
함수를 사용하면 된다.
for (i in 1..4 step 2) print(i) // "13" 출력
for (i in 4 downTo 1 step 2) print(i) // "42" 출력
마지막 요소를 포함하지 않는 범위를 만들 경우 until
함수를 사용하면 된다.
for (i in 1 until 10) { // i in [1, 10), 10 은 제외된다
println(i)
}
동작 방식
범위는 라이브러리의 공통 인터페이스인 ClosedRange<T>
를 구현한다.
ClosedRange<T>
는 comparable 타입에 대해 수학적 의미의 닫힌 간격을 나타낸다. 범위에는 start
및 endInclusive
두 개의 끝점을 가진다. 주 연산은 contains
이며 일반적으로 in/!in 연산자 형식으로 사용한다.
정수 타입 프로그레션 (IntProgression
, LongProgression
, CharProgression
) 은 산술 진행을 나타낸다. 프로그레션은 first
, last
, 0이 아닌 step
으로 정의된다. 첫 번재 요소는 first
이고, 다음 요소는 이전 요소와의 step
을 더한 값이다. last
요소는 프로그레션이 비어 (empty) 있지 않으면 항상 도달한다.
프로그레션은 Iterable<N>
의 하위 타입으로 N
에는 Int
, Long
, Char
가 올수 있으며 for-loop나 map
, filter
와 같은 함수에서 사용가능 하다. 프로그레션
을 통한 반복은 Java/JavaScript의 인덱싱된 for-loop와 동일하다.
for (int i = first; i != last; i += step) {
// ...
}
정수 타입에서 ..
연산자는 ClosedRange<T>
와 *Progression
을 모두 구현한 객체를 생성한다.
예를들어, IntRange는 ClosedRange<Int>를 구현하고 IntProgression을 확장하므로 IntProgression에 정의된 모든 오퍼레이션을 IntRange에서도 사용 가능하다.
downTo()
및 step()
함수의 결과는 항상 *Progression
이다.
프로그레션은 companion 객체에 정의된 fromClosedRange
함수로 생성된다.
IntProgression.fromClosedRange(start, end, step)
양수 step
기준으로 end
값보다 크지않은 최대값 또는 음수 step
기준으로 end
보다 작지않은 최소값을 찾기위해 (last - first) % step == 0
와 같은 식을 사용해서 프로그레션의 last
요소를 계산한다.
유틸리티 함수
class Int {
//...
operator fun rangeTo(other: Long): LongRange = LongRange(this, other)
//...
operator fun rangeTo(other: Int): IntRange = IntRange(this, other)
//...
}
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
fun Long.downTo(other: Int): LongProgression {
return LongProgression.fromClosedRange(this, other.toLong(), -1L)
}
fun Byte.downTo(other: Int): IntProgression {
return IntProgression.fromClosedRange(this.toInt(), other, -1)
}
fun IntProgression.reversed(): IntProgression {
return IntProgression.fromClosedRange(last, first, -step)
}
fun IntProgression.step(step: Int): IntProgression {
if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
fun CharProgression.step(step: Int): CharProgression {
if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
return CharProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
(1..12 step 2).last == 11 // progression with values [1, 3, 5, 7, 9, 11]
(1..12 step 3).last == 10 // progression with values [1, 4, 7, 10]
(1..12 step 4).last == 9 // progression with values [1, 5, 9]