【问题标题】:How to cycle a list infinitely and lazily in Kotlin?如何在 Kotlin 中无限懒惰地循环列表?
【发布时间】:2017-04-17 17:57:39
【问题描述】:

我有一个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 中:

问题:

  • 我可以通过 Kotlin 中的列表/集合执行 cycle 吗?
  • 在 Kotlin 中是否有惯用的方法来进行负索引查找?

【问题讨论】:

    标签: clojure kotlin lazy-sequences


    【解决方案1】:

    转述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. 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()
    

    【讨论】:

      【解决方案2】:

      自定义序列,可以无限重复给定序列或列表,可以很容易地用flatten 编写:

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

      【讨论】:

      • 看起来很简单!谢谢,我试试看。
      • 为什么不只是generateSequence { this }.flatten()?你需要种子吗?
      • @arekolek generateSequence 没有种子被限制只能迭代一次。如果有种子,那么序列可以迭代多次,每次都从种子开始。 kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/…
      【解决方案3】:

      您可以在 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
      

      【讨论】:

        【解决方案4】:

        这里是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(_) 可以让您以编程方式访问枚举的成员。

        【讨论】:

        • 我认为最好的答案应该是这个stackoverflow.com/questions/40938716/…删除最后一个toList()调用,应该是懒惰
        • 好吧,.toList() 仅用于演示目的。没有它,我的示例将打印“kotlin.sequences.TakeSequence@1fb3ebeb”。它不是解决方案的一部分。此外,我的解决方案索引到一个集合中,我更喜欢 flatten() 解决方案。 flatten() 解决方案的好处是内联很好。
        猜你喜欢
        • 2021-01-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-24
        • 2014-05-31
        • 1970-01-01
        相关资源
        最近更新 更多