如何在Kotlin中无限地循环清单



我有一个directions的列表,想在我右转或左转时找到下一个方向。这是我拥有的工作代码:

enum class Turn { R, L }
enum class Direction { N, E, S, W }
val directionsInRightTurnOrder = listOf(Direction.N, Direction.E, Direction.S, Direction.W)
private fun calculateNextHeading(heading: Direction, turn: Turn): Direction {
    val currentIndex = directionsInRightTurnOrder.indexOf(heading)
    var nextIndex = currentIndex + if (turn == Turn.R) 1 else -1
    if (nextIndex >= directionsInRightTurnOrder.size)
        nextIndex = directionsInRightTurnOrder.size - nextIndex
    if (nextIndex < 0)
        nextIndex += directionsInRightTurnOrder.size
    return directionsInRightTurnOrder.get(nextIndex)
}
  1. 但是,如果我可以获取directionsInRightTurnOrder列表并循环循环(懒惰),这将变得更加简单,更容易阅读。在Clojure中,我可以使用clojure.core/cycle:
(take 5 (cycle ["a" "b"]))
# ("a" "b" "a" "b" "a")
  1. 另一件事会有所帮助的是,如果我可以使用负面索引在列表中查找列表,例如Ruby或Python:

    • http://rubyquicktips.com/post/996814716/use-negative-array-indices
    • Python列表的负索引

问题:

  • 我可以通过kotlin中的列表/集合做cycle吗?
  • 是否有一种惯用的方法在Kotlin中进行负面索引?

这是cycle

fun <T : Any> cycle(vararg xs: T): Sequence<T> {
    var i = 0
    return generateSequence { xs[i++ % xs.size] }
}
cycle("a", "b").take(5).toList() // ["a", "b", "a", "b", "a"]

这是您可以实现转弯应用程序的方式:

enum class Turn(val step: Int) { L(-1), R(1) }
enum class Direction {
    N, E, S, W;
    fun turned(turn: Turn): Direction {
        val mod: (Int, Int) -> Int = { n, d -> ((n % d) + d) % d }
        return values()[mod(values().indexOf(this) + turn.step, values().size)]
    }
}

听起来像modulo是您想要的 - 负索引包裹。在Kotlin的stdlib中找不到它,所以我带来了自己的。

Direction.N
    .turned(Turn.R) // E
    .turned(Turn.R) // S
    .turned(Turn.R) // W
    .turned(Turn.R) // N
    .turned(Turn.L) // W

Enum#values()Enum#valueOf(_)是让您以编程方式访问枚举成员的方法。

自定义序列,它可以无限期地以flatten的形式很容易地重复给定的序列或列表:

fun <T> Sequence<T>.repeatIndefinitely(): Sequence<T> = 
    generateSequence(this) { this }.flatten()
fun <T> List<T>.repeatIndefinitely(): Sequence<T> =
    this.asSequence().repeatIndefinitely()

您可以通过生成一个反复返回列表/集合然后变平的序列来循环浏览Kotlin中的列表/集合。例如:

generateSequence { listOf("a", "b") }.flatten().take(5).toList()
// [a, b, a, b, a]

您可以定义自己的Modulo功能,以胁迫负数和正数以访问列表中元素的有效索引(另请参见Google Guava的IntMath.mod(int, int)):

infix fun Int.modulo(modulus: Int): Int {
    if (modulus <= 0) throw ArithmeticException("modulus $modulus must be > 0")
    val remainder = this % modulus
    return if (remainder >= 0) remainder else remainder + modulus
}
val list = listOf("a", "b", "c", "d")
list[-1 modulo list.size] // last element
list[-2 modulo list.size] // second to last element
list[+9 modulo list.size] // second element
list[-12 modulo list.size] // first element

在kotlin Slack上的讨论:

  • 使用List#modulo将使此更简单,但不像cycle那样优雅,因为负面索引仍需要处理。

  • 实现周期性列表的一个选项是Sequence。但是,需要使用generateSequence编写自定义Sequence。我们认为这是这种情况的过度杀伤。

最终我去了:

  1. 使Direction了解nextprevious
enum class Direction {
    N, E, S, W;
    private val order by lazy { listOf(N, E, S, W) }
    fun add(turns: Int): Direction {
        val currentIndex = order.indexOf(this)
        var nextIndex = (currentIndex + turns) % order.size
        return order.possiblyNegativeLookup(nextIndex)
    }
    fun subtract(turns: Int) = add(-1 * turns)
    fun next(): Direction = add(1)
    fun previous(): Direction = subtract(1)
}
  1. 使用possiblyNegativeLookup扩展List
fun <E> List<E>.possiblyNegativeLookup(i: Int): E {
    return if (i < 0) this[this.size + i] else this[i]
}

因此,最终代码变成:

val nextHeading = if (move.turn == Turn.R) heading.next() else heading.previous()

相关内容

  • 没有找到相关文章

最新更新