Scala - 从给定元素开始在有限序列上循环

时间:2014-02-17 21:18:32

标签: scala scala-collections

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

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

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

由于我在一个热循环中使用它,一遍又一遍地开始,并且实际的序列非常冗长,我担心dropWhile将花费我很多。
有没有办法可以加快速度呢? 我是以正确的方式做到的吗?

修改

给出了一些好的答案,表明我不应该使用Stream。现在的核心问题是:如何避免使用dropWhile或类似功能?

5 个答案:

答案 0 :(得分:3)

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

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()
}

答案 1 :(得分:2)

这是一个隐式类,允许您在任何.looped上调用Traversable方法。我相信你会发现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

答案 2 :(得分:1)

您应该使用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

答案 3 :(得分:1)

另一种方法,其中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

答案 4 :(得分:1)

为避免尽可能重新计算:

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))

然而,这不适用于Iterator s;在这种情况下,我会对Dave Rando的答案进行修改。