programowanieProgramista Backend

Opisz mechanizm zakresów numerycznych w Kotlin: jak działają Range i Progression, jak tworzyć własne zakresy i w jakich scenariuszach są one wygodne do stosowania?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Historia pytania

Od samego początku w Kotlin wprowadzono zakresy (Range) w celu uproszczenia operacji na sekwencyjnych zbiorach wartości — jest to dziedziczone z języków o zwięzłej składni do pracy z liczbami (np. Python). Dodatkowo, mechanizm Range został rozszerzony do Progression, pozwalając na określenie kroku iteracji i wspierając różne typy liczb, znaków.

Problem

Iteracja po liczbach całkowitych, literach, punktach czasowych itp. często wymaga specjalnej składni i solidnego wsparcia w standardowej bibliotece, w przeciwnym razie kod staje się nieporęczny, nieczytelny i podatny na błędy przy granicach.

Rozwiązanie

W Kotlin istnieją standardowe typy IntRange, CharRange i LongRange, a także Progression do iteracji krokowej. Ponadto można zdefiniować zakresy dla dowolnych porównywalnych typów.

// Prosty zakres for (i in 1..5) print(i) // 12345 // Zakres z krokiem for (i in 1..10 step 2) print(i) // 13579 // Zakres odwrócony for (i in 5 downTo 1) print(i) // 54321 // Własny zakres (np. dla Version) data class Version(val major: Int, val minor: Int): Comparable<Version> { override fun compareTo(other: Version): Int = compareValuesBy(this, other, Version::major, Version::minor) } operator fun ClosedRange<Version>.iterator(): Iterator<Version> = object : Iterator<Version> { var current = start override fun hasNext() = current <= endInclusive override fun next() = current.also { current = Version(current.major, current.minor + 1) } } val v1 = Version(1, 0) val v2 = Version(1, 3) for (v in v1..v2) println(v)

Kluczowe cechy:

  • Standardowa składnia zakresów: .., downTo, until, step
  • Praca z typami numerycznymi, znakowymi, użytkowymi
  • Zastosowanie w pętlach, sprawdzaniu przynależności, podziałach, walidacjach

Pytania z pułapkami.

Czym różni się wyrażenie 1..5 od 1 until 5?

1..5 obejmuje oba końce zakresu: 1,2,3,4,5. 1 until 5 nie obejmuje ostatniego elementu: 1,2,3,4.

Czy można zdefiniować zakres z krokiem mniejszym od zera przy użyciu step?

Nie. Dla malejących zakresów użyj konstrukcji downTo, a następnie step: 5 downTo 1 step 2 (otrzymasz 5,3,1).

Czy można używać zakresów z typami, które nie implementują Comparable?

Nie. Aby utworzyć zakres, typ musi wspierać porównanie. W przeciwnym razie kompilator nie pozwoli na definicję.

Typowe błędy i antywzorce

  • Używanie until zamiast .. lub odwrotnie, zamieszanie z włączeniem granic
  • Definiowanie ujemnego kroku dla rosnącego zakresu (step nie czyni zakres odwróconym)
  • Nieprzestrzeganie wymagań Comparable dla typów użytkowych w zakresie

Przykład z życia

Negatywny przypadek

W kodzie zamiast 1 until n+1 użyto 1..n. Otrzymujemy dodatkowy element, pętla wykracza poza dopuszczalny zakres.

Plusy:

  • Niezamierzona chwytność skrajnego przypadku

Minusy:

  • Błędy w podziałach, nieprawidłowe obliczenia sum na granicy

Pozytywny przypadek

Użyto for (i in 0 until n) do indeksowania tablicy o długości n, zakres dokładnie pokrywa się z dozwolonymi wartościami indeksów.

Plusy:

  • Wykluczenie błędów dostępu poza granice tablicy
  • Zwiększona czytelność

Minusy:

  • Wymagana pamięć różnicy między .. a until przy przechodzeniu między językami