【问题标题】:How do you rotate (circular shift) of a Scala collection你如何旋转(循环移位)Scala 集合
【发布时间】:2015-12-18 19:23:55
【问题描述】:

可以使用 for 循环很容易、干净地做到这一点。例如,如果我想从每个元素遍历 Seq 回到自身,我会执行以下操作:

val seq = Seq(1,2,3,4,5)

for (i <- seq.indices) {
    for (j <- seq.indices) {
        print(seq(i + j % seq.length))
    }
}

但是当我在寻找 fold 的集合时,我想知道是否有更惯用的方法。递归方法可以让我避免任何vars。但基本上,我想知道是否可能出现以下情况:

seq.rotatedView(i)

这将创建一个旋转的视图,例如旋转位(或循环移位)。

【问题讨论】:

  • 你想要的输出是什么?
  • 给定Seq('a', 'b', 'c'),假设我想输出“bca”或“cab”。老实说,我只是在寻找一种通用的方法来创建一个“旋转视图”,它可以让我折叠它。

标签: scala collections iteration scala-collections idioms


【解决方案1】:

是不是像下面这样:

scala> def rotatedView(i:Int)=Seq(1,2,3,4,5).drop(i)++Seq(1,2,3,4,5).take(i)
rotatedView: (i: Int)Seq[Int]

scala> rotatedView(1)
res48: Seq[Int] = List(2, 3, 4, 5, 1)

scala> rotatedView(2)
res49: Seq[Int] = List(3, 4, 5, 1, 2)

【讨论】:

  • 同时在droptake前面调用.view会提高性能,因为不会复制集合。
【解决方案2】:

这应该以相当通用的方式进行,并允许任意旋转:

def rotateLeft[A](seq: Seq[A], i: Int): Seq[A] = {
    val size = seq.size
    seq.drop(i % size) ++ seq.take(i % size)
}

def rotateRight[A](seq: Seq[A], i: Int): Seq[A] = {
    val size = seq.size
    seq.drop(size - (i % size)) ++ seq.take(size - (i % size))
}

这个想法很简单,向左旋转,drop 从左边开始第一个 i 元素,然后从左边再次 take 它们以相反的顺序连接它们。如果您不介意计算集合的大小,可以对大小进行模运算,以允许 i 任意。

scala> rotateRight(seq, 1)
res34: Seq[Int] = List(5, 1, 2, 3, 4)

scala> rotateRight(seq, 7)
res35: Seq[Int] = List(4, 5, 1, 2, 3)

scala> rotateRight(seq, 70)
res36: Seq[Int] = List(1, 2, 3, 4, 5)

同样,你可以使用splitAt:

def rotateLeft[A](seq: Seq[A], i: Int): Seq[A] = {
    val size = seq.size
    val (first, last) = seq.splitAt(i % size)
    last ++ first
}

def rotateRight[A](seq: Seq[A], i: Int): Seq[A] = {
    val size = seq.size
    val (first, last) = seq.splitAt(size - (i % size))
    last ++ first
}

为了使其更加通用,使用 enrich my library 模式:

import scala.collection.TraversableLike
import scala.collection.generic.CanBuildFrom

implicit class TraversableExt[A, Repr <: TraversableLike[A, Repr]](xs: TraversableLike[A, Repr]) {

    def rotateLeft(i: Int)(implicit cbf: CanBuildFrom[Repr, A, Repr]): Repr = {
        val size = xs.size
        val (first, last) = xs.splitAt(i % size)
        last ++ first
    }

    def rotateRight(i: Int)(implicit cbf: CanBuildFrom[Repr, A, Repr]): Repr = {
        val size = xs.size
        val (first, last) = xs.splitAt(size - (i % size))
        last ++ first
    }

}

scala> Seq(1, 2, 3, 4, 5).rotateRight(2)
res0: Seq[Int] = List(4, 5, 1, 2, 3)

scala> List(1, 2, 3, 4, 5).rotateLeft(2)
res1: List[Int] = List(3, 4, 5, 1, 2)

scala> Stream(1, 2, 3, 4, 5).rotateRight(1)
res2: scala.collection.immutable.Stream[Int] = Stream(5, ?)

请记住,这些不一定都是针对性能优化的,而且它们也不能与无限集合一起使用(没有一个可以)。

【讨论】:

  • 每个人都介意任意集合的大小,因为它强制使用惰性数据结构。即使它hasDefiniteSize,除非你知道它很便宜,否则通常避免。
  • 连根拔起,因为,就API而言,这是最方便的。
【解决方案3】:

根据 OP 的评论,他们想要折叠它,这里有一个稍微不同的看法,避免先计算序列的长度。

定义一个遍历旋转序列的迭代器

class RotatedIterator[A](seq: Seq[A], start: Int) extends Iterator[A] {
  var (before, after) = seq.splitAt(start)
  def next = after match {
    case Seq()  =>
      val (h :: t) = before; before = t; h
    case h :: t => after = t; h
  }
  def hasNext = after.nonEmpty || before.nonEmpty
}

并像这样使用它:

val seq = List(1, 2, 3, 4, 5)  
val xs = new RotatedIterator(seq, 2)
println(xs.toList)         //> List(3, 4, 5, 1, 2)

【讨论】:

  • 用类似的方法创建一个“旋转的”View 会不会很困难?不要求实施。
  • 我真的不知道,但我怀疑是这样。您需要实现更多操作,并更深入地集成到 Scala 集合层次结构中。如果不计算集合的长度,也很难了解如何实现索引访问。
  • 接受这个答案,因为它的懒惰性质更实用。
  • @NadirMuzaffar,或者不:)(不要抱怨,您可以选择最符合您需求的答案)
  • 不不!我可以发誓我把它换回来了=P。我最终使用了 Binzi 的方法,但在现实世界中,我会使用你的方法。我很惊讶在这么长时间之后我被允许切换。
【解决方案4】:

这里是一个班轮解决方案

def rotateRight(A: Array[Int], K: Int): Array[Int] = {
    if (null == A || A.size == 0) A else (A drop A.size - (K % A.size)) ++ (A take A.size - (K % A.size))
}
rotateRight(Array(1,2,3,4,5), 3)

【讨论】:

    【解决方案5】:

    给定:

    val seq = Seq(1,2,3,4,5)
    

    解决办法:

    seq.zipWithIndex.groupBy(_._2<3).values.flatMap(_.map(_._1))
    

    seq.zipWithIndex.groupBy(_._2<3).values.flatten.map(_._1)
    

    结果:

    List(4, 5, 1, 2, 3)
    
    1. 如果旋转大于集合长度 - 我们需要使用rotation%length,如果比公式(rotation+1)%length 负数并取绝对值。
    2. 效率不高

    【讨论】:

    • 我看到三个问题: 1 - 如果旋转值大于seq.length,则不会发生旋转。 (它不会“连续”旋转。) 2 - seq 至少遍历 3 次(zipgroupmap)。 3 - 语言是否保证总是首先提取true 值?下一个 Scala 版本可以更改检索 values 的顺序吗?
    • 感谢您的评论 - 1. 我想 shift%length 应该修复它 2. 对,它不是内置的,但是 O(n) 3. 没有保证
    【解决方案6】:

    这是一种相当简单且惯用的 Scala 集合编写方式:

    def rotateSeq[A](seq: Seq[A], isLeft: Boolean = false, count: Int = 1): Seq[A] =
      if (isLeft)
        seq.drop(count) ++ seq.take(count)
      else
        seq.takeRight(count) ++ seq.dropRight(count)
    

    【讨论】:

      【解决方案7】:

      我们可以简单地使用foldLeft 来反转一个列表,如下所示。

        val input = List(1,2,3,4,5)
      
        val res = input.foldLeft(List[Int]())((s, a) => { List(a) ++: s})
      
        println(res) // List(5, 4, 3, 2, 1)
      

      【讨论】:

      • 反转List 不是问题所要求的。在回答这么古老的问题时,最好在提交新答案之前查看所有当前答案。
      【解决方案8】:

      另一种尾递归方法。当我使用 JMH 对其进行基准测试时,它比基于 drop/take 的解决方案快大约 2 倍:

      def rotate[A](list: List[A], by: Int): List[A] = {
      
          @tailrec
          def go(list: List[A], n: Int, acc: List[A]): List[A] = {
      
            if(n > 0) {
              list match {
                case x :: xs => go(xs, n-1, x :: acc)
              }
            } else {
              list ++ acc.reverse
            }
      
          }
      
          if (by < 0) {
            go(list, -by % list.length, Nil)
          } else {
            go(list, list.length - by % list.length, Nil)
          }    
      }
      
      //rotate right
      rotate(List(1,2,3,4,5,6,7,8,9,10), 3) // List(8, 9, 10, 1, 2, 3, 4, 5, 6, 7) 
      
      //use negative number to rotate left
      rotate(List(1,2,3,4,5,6,7,8,9,10), -3) // List(4, 5, 6, 7, 8, 9, 10, 1, 2, 3)
      

      【讨论】:

        【解决方案9】:

        这是一段简单的代码

          object tesing_it extends App 
        {
        val one = ArrayBuffer(1,2,3,4,5,6)
        val  i = 2  //the number of index you want to move
        
        
        
         for(z<-0 to i){
           val y = 0
           var x =  one += one(y)
           x = x -= x(y)
           println("for seq after process " +z +" " + x)
          }
        
        
        println(one)
        
        }
        

        结果:

        for seq after process 0 ArrayBuffer(2, 3, 4, 5, 6, 1)

        for seq after process 1 ArrayBuffer(3, 4, 5, 6, 1, 2)

        for seq after process 2 ArrayBuffer(4, 5, 6, 1, 2, 3)

        ArrayBuffer(4, 5, 6, 1, 2, 3)

        【讨论】:

          【解决方案10】:

          如果您不需要验证“偏移量”,则另一种解决方案:

             def rotate[T](seq: Seq[T], offset: Int): Seq[T] = Seq(seq, seq).flatten.slice(offset, offset + seq.size)

          【讨论】:

          【解决方案11】:

          一个简单的方法是将序列与自身连接,然后取所需的slice

          (seq ++ seq).slice(start, start + seq.length)
          

          这只是drop/take 版本的变体,但可能更清晰一些。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-10-20
            • 1970-01-01
            • 1970-01-01
            • 2014-02-24
            • 2022-01-23
            • 2021-03-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多