如何在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 InRightTurnOrder列表中循环,并且无限循环(和懒惰地),这将会变得更加简单和容易阅读。 在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] 

您可以定义自己的模数函数,将正数和负数强制转换为有效索引,以访问列表中的元素(另请参阅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 。 但是,自定义Sequence将需要写入,或使用generateSequence 。 我们认为这是一个矫枉过正的情况。

最后我跟着去了:

  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. 使用possibleNegativeLookup扩展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()