【问题标题】:Scala - cycling over a finite sequence starting from a given elementScala - 从给定元素开始循环有限序列
【发布时间】:2014-02-18 18:57:45
【问题描述】:

我需要有效地循环一个固定的序列,从它的任何一个元素开始,而不是总是从头开始。
我一直在探索的解决方案是:

val directions = List("north", "east", "south", "west")
val cycle = Stream.continually(directions).flatten

cycle dropWhile (_ != "south") take 12 foreach println

由于我在热循环中使用它,一遍又一遍地开始,而且实际序列相当长,我担心dropWhile 会花费我不少钱。
有什么办法可以加快速度吗?
我的做法是否正确?

编辑

给出了一些好的答案,建议我不应该使用Streams。现在的中心问题变成了:如何避免使用dropWhile 或类似功能?

【问题讨论】:

  • 该列表是在程序运行时动态生成的输入还是固定列表?完成dropWhile 后,您不能重复使用对Stream 的相同引用吗?是什么阻止了这种情况?
  • 任务开始时列表内容固定。基本上,我必须浏览可能的关系列表(例如HunterHunted),搜索具有给定属性的第一个元素。一旦找到,我将反转关系 (HuntedHunter) 并从下一个开始......所以我可以重复使用相同的Stream,但我必须一次又一次地使用dropWhile。跨度>

标签: scala scala-collections


【解决方案1】:

下面的示例对固定序列的值进行索引,以避免每次启动新流时遍历列表。

package rando

object Faster extends App {
  val directions = Vector("north", "east", "south", "west")
  val index = directions.zipWithIndex.map { case (d, i) => d -> i }.toMap
  def streamWithStart(start: String): Stream[String] =
    directions.drop(index(start)).toStream.append(Stream.continually(directions).flatten)
  streamWithStart("east") take 10 reduce(_+" "+_) foreach print; println()
  streamWithStart("north") take 10 reduce(_+" "+_) foreach print; println()
  streamWithStart("west") take 10 reduce(_+" "+_) foreach print; println()
  streamWithStart("south") take 10 reduce(_+" "+_) foreach print; println()
}

【讨论】:

    【解决方案2】:

    这是一个隐式类,允许您在任何Traversable 上调用.looped 方法。我相信你会发现 Iterator 它返回的效率比扁平化 Stream.continually 的结果要高效得多。

    import scala.reflect.ClassTag
    
    /** Turns a Traversable into an infinite loop. */
    implicit class TraversableLooper[A:ClassTag]( val items:Traversable[A] ) {
      def looped:Iterator[A] =
        if ( items.isEmpty )
          sys error "<empty>.looped"
        else
          new Iterator[A] {
          val array = items.toArray
          val numItems = array.length
          var i = -1
          def hasNext = true
          def next = {
            i += 1
            if ( i == numItems ) i = 0
            array(i)
          }
        }
    }
    

    这里正在使用:

    scala> val directions = List("north", "east", "south", "west")
    directions: List[String] = List(north, east, south, west)
    
    scala> directions.looped dropWhile (_ != "south") take 12 foreach println
    south
    west
    north
    east
    south
    west
    north
    east
    south
    west
    north
    east
    

    另外,制作循环之前尽可能多地做,例如dropWhile:

    ( directions.looped dropWhile (_ != "south") take 4 ).toSeq.looped
    

    【讨论】:

    • 这很好,但它并没有消除调用dropWhile的需要
    • 抱歉,如果不清楚,但最后一条语句的重点不是关于消除对dropWhile 的调用。关键是要执行dropWhile before 捕获Iterator 的值(在第二次调用looped 中),这样您就不必在@987654334 上运行过滤器@ 得到你想要的值。当然,我不知道这在您的真实代码中是否可行,但是对于给出的示例是可行的;您从所需的值开始创建列表,然后循环 that.
    • 是的,我明白了...您可以将我之前的评论视为不接受答案的理由 ;-) 这是因为在我的代码中,迭代器的创建也会进入循环跨度>
    【解决方案3】:

    您应该使用Iterator 而不是StreamStream 把一切都记在内存里,变得越来越大。

    scala> val directions = List("north", "east", "south", "west")
    directions: List[String] = List(north, east, south, west)
    
    scala> val cycle = Iterator.continually(directions).flatten
    cycle: Iterator[String] = non-empty iterator
    
    scala> cycle dropWhile (_ != "south") take 2 foreach println
    south
    west
    
    scala> cycle dropWhile (_ != "east") take 2 foreach println
    east
    south
    

    【讨论】:

    • 很好的建议,但请参阅对 AmigoNico 回答的评论
    【解决方案4】:

    还有另一种方法,其中directions 键值south 向上移动到列表的头部,然后我们在移动的列表上迭代例如4 次,

    implicit class AddCycleToList[A](val list: List[A]) extends AnyVal {
    
      def cycle(times: Int, from: A): Unit = {
        val shiftBy = list span { _ != from }
        val shifted = shiftBy._2 ++ shiftBy._1
        for (i <- 1 to times) shifted foreach println
      }
    }
    

    因此

    scala> val directions = List("north", "east", "south", "west")
    directions: List[String] = List(north, east, south, west)
    
    scala> directions.cycle(4, "south")
    south
    west
    north
    east
    south
    west
    north
    east
    south
    west
    north
    east
    south
    west
    north
    east
    

    【讨论】:

      【解决方案5】:

      尽可能避免重新计算:

      val cycle = Stream.continually(directions).flatten
      val cachedStreams = collections.mutable.Map.empty[String, Stream[String]]
      
      def cycleStartingWith(direction: String) = 
        cachedStreams.getOrElseUpdate(direction, cycle.dropWhile(_ != direction))
      

      但是,这不适用于Iterators;在这种情况下,我会选择 Dave Rando 的答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-31
        • 1970-01-01
        • 1970-01-01
        • 2023-02-21
        • 2022-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多