如何在科特林无限和懒惰地循环列表?
我有一个directions
列表,并希望find下一个方向,当我左转或右转。 这是我有的工作代码:
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) }
- 然而,如果我可以在
directionsInRightTurnOrder
InRightTurnOrder列表中循环,并且无限循环(和懒惰地),那么这将更加简单易读。 在Clojure中,我可以这样做,使用clojure.core / cycle :
(take 5 (cycle ["a" "b"])) # ("a" "b" "a" "b" "a")
-
另一件有用的事情是,如果我可以使用负向索引来查找列表,比如在Ruby或Python中:
- http://rubyquicktips.com/post/996814716/use-negative-array-indices
- Python列表的否定索引
题:
- 我可以通过Kotlin中的列表/收集
cycle
吗? - 在Kotlin中是否有一种惯用的方式来进行负向索引查找?
这是cycle
:
fun cycle(vararg xs: T): Sequence { 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 Sequence .repeatIndefinitely(): Sequence = generateSequence(this) { this }.flatten() fun List .repeatIndefinitely(): Sequence = 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
。 我们认为这是一个矫枉过正的情况。
最后我跟着去了:
- 使
Direction
知道下next
和previous
:
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) }
- 使用possibleNegativeLookup扩展
List
:
fun List .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()