如何在 Kotlin 中无限延迟地循环列表?
How to cycle a list infinitely and lazily in 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)
}
- 但是,如果我可以使用
directionsInRightTurnOrder
列表并无限地(并且懒惰地)循环它,那么这将更加简单易读。在 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
- Negative index to Python list
问题:
- 我可以通过 Kotlin 中的 list/collection 执行
cycle
吗?
- 在 Kotlin 中是否有一种惯用的方法来进行负索引查找?
转述 kotlin Slack 上的讨论:
使用List#modulo
会使这更简单,但仍然不如cycle
优雅,因为仍然需要处理负索引。
实现循环列表的一个选项是 Sequence
。但是,需要使用 generateSequence
编写或生成自定义 Sequence
。我们认为这对这种情况有点矫枉过正。
最终我选择了:
- 让
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)
}
- 用
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()
自定义序列,无限重复给定的序列或列表可以很容易地写成 flatten
:
fun <T> Sequence<T>.repeatIndefinitely(): Sequence<T> =
generateSequence(this) { this }.flatten()
fun <T> List<T>.repeatIndefinitely(): Sequence<T> =
this.asSequence().repeatIndefinitely()
您可以在 Kotlin 中循环遍历 list/collection,方法是生成一个重复 returns 和 list/collection 的序列,然后将其展平。例如:
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
这里是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 的标准库中找不到它,所以我自带了。
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(_)
允许您以编程方式访问枚举的成员。
我有一个 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)
}
- 但是,如果我可以使用
directionsInRightTurnOrder
列表并无限地(并且懒惰地)循环它,那么这将更加简单易读。在 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
- Negative index to Python list
问题:
- 我可以通过 Kotlin 中的 list/collection 执行
cycle
吗? - 在 Kotlin 中是否有一种惯用的方法来进行负索引查找?
转述 kotlin Slack 上的讨论:
使用
List#modulo
会使这更简单,但仍然不如cycle
优雅,因为仍然需要处理负索引。实现循环列表的一个选项是
Sequence
。但是,需要使用generateSequence
编写或生成自定义Sequence
。我们认为这对这种情况有点矫枉过正。
最终我选择了:
- 让
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)
}
- 用
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()
自定义序列,无限重复给定的序列或列表可以很容易地写成 flatten
:
fun <T> Sequence<T>.repeatIndefinitely(): Sequence<T> =
generateSequence(this) { this }.flatten()
fun <T> List<T>.repeatIndefinitely(): Sequence<T> =
this.asSequence().repeatIndefinitely()
您可以在 Kotlin 中循环遍历 list/collection,方法是生成一个重复 returns 和 list/collection 的序列,然后将其展平。例如:
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
这里是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 的标准库中找不到它,所以我自带了。
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(_)
允许您以编程方式访问枚举的成员。