Zakresy i iteracja

Kolejny mały temat, zanim powrócimy do funkcji. Jak wiemy, Kotlin pozwala na przeciążanie operatorów. Jednym z nich jest operator podwójnej kropki – .., który służy do stworzenia zakresu. A do czego może posłużyć nam zakres? Na przykład do uproszczenia zapisu w pętli:

Listing 1. Wykorzystanie zakresu Int

fun main(args: Array<String>) {
    for (i in 1..5)
        println(i)
}

Własne zakresy

Oczywiście możemy utworzyć własny zakres. W tym celu należy przeciążyć operator .. oraz utworzyć klasę rozszerzającą ClosedRange. Dodatkowo klasa, która będzie wyposażona w zakres musi implementować Comparable.

Listing 2. Przygotowanie klasy Version do działania w zakresach

data class Version(val v: Int) : Comparable<Version> {
    override fun compareTo(other: Version): Int {
        return this.v.compareTo(other.v)
    }
}

Kolejnym krokiem jest dodanie odpowiedniego operatora.

Listing 3. Dodanie operatora .. do klasy Version

operator fun Version.rangeTo(b: Version): VersionRange {
    return VersionRange(this, b)
}

W tym miejscu wiemy już, że potrzebujemy klasy VersionRange, która będzie implementować ClosedRange.

Listing 4. Klasa VersionRange

class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>

W ten sposób mamy już skonfigurowany nasz zakres. Jednak nadal nie możemy użyć go w pętli. Sam zakres opisuje tylko pewien zbiór wartości. Nie mówi nic o tym, jak należy się w nim poruszać. W tym celu należy zaimplementować interfejs Iterable:

Listing 5. Klasa VersionRange

class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>, Iterable<Version>{

    override fun iterator(): Iterator<Version> = VersionIterator(start, endInclusive)

}

Implementacja interfejsu pociąga za sobą konieczność stworzenia klasy VersionIterator:

Listing 6. Klasa VersionIterator

class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>, Iterable<Version>{

    override fun iterator(): Iterator<Version> = VersionIterator(start, endInclusive)

    private class VersionIterator(first: Version, last: Version) : Iterator<Version> {
        private var next = first
        private val finalElement = last
        private var hasNext: Boolean = first <= last

        override fun hasNext(): Boolean = hasNext

        override fun next(): Version {
            val value = next
            if (value == finalElement) {
                hasNext = false
            } else {
                next++
            }
            return value
        }

    }
}

operator fun Version.inc(): Version {
    return copy(v + 1);
}

Gotowe. By móc wykorzystać ++ musieliśmy jeszcze dodać przeciążenie tego operatora, ale to już tylko czynność ekstra, by uprościć zapis. Możemy teraz napisać:

Listing 7. Przykładowe użycie

fun main(args: Array<String>) {
    for (v in Version(1)..Version(5))
        println(v)
}

Podsumowanie

Tworzenie własnych zakresów bywa przydatne. Szczególnie tam gdzie klasy domenowe mają „naturalną” właściwość do bycia zakresami. Wszelkiej maści wersje, numery seryjne, numery dokumentów są świetnymi kandydatami do tej operacji.

PS

Iterator można zaimplementować z wykorzystaniem AbstractIterator:

Listing 8. Wykorzystanie AbstractIterator

class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>, Iterable<Version> {

    override fun iterator(): Iterator<Version> = VersionIterator(start, endInclusive)

    private class VersionIterator(var first: Version, val last: Version) : AbstractIterator<Version>() {
        override fun computeNext() {
            if(first > last)
                return done();
            setNext(first++)
        }
    }
}

Czyni to kod bardziej zwięzłym.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax