코틀린에서는 범위, 수열을 쉽게 사용할 수 있는 Range, Progression 클래스가 있습니다

 

if (i in 1..4) { // equivalent of 1 <= i && i <= 4
    print(i)
}

Range는 from..to 방식으로 만들 수 있습니다.

 

for (i in 1..4) print(i)  // 1 2 3 4

정수형의 Range 클래스(IntRange, LongRange. CharRange)는 Progression도 되기 때문에 iterate가 가능합니다

 

for (i in 4 downTo 1) print(i)  // 4 3 2 1

유의할 점은 .. 로 만들 때 a <= b 조건을 만족해야 합니다. 즉 오름차순으로만 만들 수 있고, 내림차순 형태로 만들려면 downTo 메소드를 사용해야 합니다

 

for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)

// Output
// 1357
// 8642

step 메소드를 이용해 iteration step 값을 정할 수 있습니다. downTo 에서도 동일하게 사용 가능합니다

 

for (i in 1 until 10) {       // i in 1 until 10, excluding 10
    print(i)
}

range 끝 범위는 포함하고 싶지 않을 때, 예를 들어 [1, 10) 처럼 range를 만들고 싶은 경우 until 메소드를 사용합니다

 

for (i in (1..4).reversed()) print(i)

progression은 reversed 메소드를 사용해 역순으로 순회할 수 있습니다

 

커스텀 Range, Progression 클래스 만들기

커스텀 클래스가 in operator를 쓰거나 iteration이 가능하도록 만들어 봅시다

 

data class MyDate (
    val year: Int,
    val month: Int,
    val day: Int,
)

예제로 MyDate 클래스를 만들어봤습니다. in operator를 사용하려면 Comparable 인터페이스를 구현하면 됩니다.

ClosedRange<T>는 compareTo 메소드만 쓰기 때문에 따로 만들어 줄 필요는 없습니다

 

data class MyDate (
    val year: Int,
    val month: Int,
    val day: Int,
) : Comparable<MyDate> {
    override fun compareTo(other: MyDate): Int =
        when {
            year != other.year -> year - other.year
            month != other.month -> month - other.month
            else -> day - other.day
        }
}

완성입니다.

 

fun main(args: Array<String>) {
    val d1 = MyDate(2022, 1, 1)
    val d2 = MyDate(2022, 3, 10)
    val d3 = MyDate(2022, 5, 21)

    println(d2 in d1..d3)  // true
    println(d1 in d2..d3)  // false
    
    val r = d1..d3  // ClosedRange<MyDate>
}

이제 in을 이렇게 쓸 수 있습니다.

 

Iteration을 하고 싶다면 Progression 클래스를 만들어주면 됩니다.

 

data class MyDate(
    var year: Int,
    var month: Int,
    var day: Int,
) : Comparable<MyDate> {
    override fun compareTo(other: MyDate): Int =
        when {
            year != other.year -> year - other.year
            month != other.month -> month - other.month
            else -> day - other.day
        }

    // suppose every month ends on 30th day
    fun nextDate(stepDays: Int): MyDate {
        val tmp = this.copy()
        tmp.addDate(stepDays)
        return tmp
    }

    private fun addDate(stepDays: Int) {
        day += stepDays
        if (day > 30) {
            month += day / 30
            day %= 30

            if (month > 12) {
                year += month / 12
                month %= 12
            }
        }
    }

    operator fun rangeTo(other: MyDate) = MyDateProgression(this, other)
}

class MyDateIterator(
    startDate: MyDate,
    private val endDateInclusive: MyDate,
    private val stepDays: Int,
) : Iterator<MyDate> {
    private var _current = startDate

    override fun hasNext() = _current.nextDate(stepDays) <= endDateInclusive

    override fun next(): MyDate {
        val ret = _current
        _current = _current.nextDate(stepDays)
        return ret
    }
}

class MyDateProgression(
    override val start: MyDate,
    override val endInclusive: MyDate,
    private val stepDays: Int = 1,
) : Iterable<MyDate>, ClosedRange<MyDate> {
    override fun iterator() = MyDateIterator(start, endInclusive, stepDays)
    infix fun step(days: Int) = MyDateProgression(start, endInclusive, days)
}

짠 완성본입니다. (https://medium.com/kotlin-arsenal/kotlin-explained-custom-range-expressions-41537563f567 참고했습니다)

편의를 위해 매달 30일까지만 있다고 가정했습니다

Iterator와 Progression 클래스를 만들었고, 마지막으로 rangeTo 오퍼레이터를 클래스에 추가해줬습니다.

은근 귀찮네요 이건..;

 

fun main(args: Array<String>) {
    val d1 = MyDate(2022, 1, 1)
    val d2 = MyDate(2022, 5, 21)

    for (d in d1..d2 step 47) {
        println(d)
    }
}

// Output
// MyDate(year=2022, month=1, day=1)
// MyDate(year=2022, month=2, day=18)

이런식으로 쓸 수 있습니다

downTo, until은 또 추가로 구현해줘야 하네요. 이건 머 생략하겠습니다

 

Reference

- https://kotlinlang.org/docs/ranges.html

반응형