以循环方式移动序列的最佳实践

时间:2012-01-16 07:10:58

标签: arrays scala sequences

我必须实现一种数组或序列或列表,它支持最便宜的循环转发和元素回绕方式。见这个例子:

Original sequence: 1 2 3 4 5

Forwarded once: 5 1 2 3 4
Forwarded twice: 4 5 1 2 3

相同但相反的是后绕组。什么是最便宜和最Scala风格的实现方式?在Java中,我可以使用LinkedList,它会很棒...但是,我找不到Scala的任何明确答案。

此外,还必须很容易通过索引替换任何给定元素,如LinkedList。

更新

对于最快但不那么惯用的算法变体(你知道什么时候需要它),请参考PetrPudlák的答案!!!

11 个答案:

答案 0 :(得分:20)

不可变的实施

ring buffer是指向此序列的一对IndexedSeqInt指针。我提供了不可变版本的代码。请注意,并非所有可能有用的方法都已实现;比如改变IndexedSeq的内容的mutator。

通过这种实现,转移只是创建一个新对象。所以效率非常高。

示例代码

class RingBuffer[A](val index: Int, val data: IndexedSeq[A]) extends IndexedSeq[A] {
  def shiftLeft = new RingBuffer((index + 1) % data.size, data)
  def shiftRight = new RingBuffer((index + data.size - 1) % data.size, data)
  def length = data.length
  def apply(i: Int) = data((index + i) % data.size)
}

val rb = new RingBuffer(0, IndexedSeq(2,3,5,7,11))

println("plain: " + rb)
println("sl: " + rb.shiftLeft)
println("sr: " + rb.shiftRight)

输出

plain: Main(2, 3, 5, 7, 11)
sl: Main(3, 5, 7, 11, 2)
sr: Main(11, 2, 3, 5, 7)

与可变实现的性能比较

如果您需要性能,OP会提到您应该查看可变实现(例如this answer)。事实并非如此。一如既往:这取决于。

不可变

  • 更新:O(log n),这基本上是基础IndexedSeq的更新复杂性;
  • 转移:O(1),还涉及创建一个可能需要花费一些周期的新对象

可变的

  • 更新:O(1),阵列更新,尽可能快
  • 转移:O(n),你必须触摸一次所有元素;由于常数因子
  • ,原始数组上的快速实现可能仍会胜过小数组的不可变版本

答案 1 :(得分:9)

scala> val l = List(1,2,3,4,5)
l: List[Int] = List(1, 2, 3, 4, 5)

scala> val reorderings = Stream.continually(l.reverse).flatten.sliding(l.size).map(_.reverse)
reorderings: Iterator[scala.collection.immutable.Stream[Int]] = non-empty iterator

scala> reorderings.take(5).foreach(x => println(x.toList))
List(1, 2, 3, 4, 5)
List(5, 1, 2, 3, 4)
List(4, 5, 1, 2, 3)
List(3, 4, 5, 1, 2)
List(2, 3, 4, 5, 1)

答案 2 :(得分:5)

我自己需要这样的操作,在这里。方法rotate将给定的索引序列(数组)向右旋转(负值向左移动)。该过程就位,因此不需要额外的内存,并且修改了原始数组。

它根本不是Scala特有的或功能性的,它意味着非常快。

import annotation.tailrec;
import scala.collection.mutable.IndexedSeq

// ...

  @tailrec
  def gcd(a: Int, b: Int): Int =
    if (b == 0) a
    else gcd(b, a % b);

  @inline
  def swap[A](a: IndexedSeq[A], idx: Int, value: A): A = {
    val x = a(idx);
    a(idx) = value;
    return x;
  }

  /**
   * Time complexity: O(a.size).
   * Memory complexity: O(1).
   */
  def rotate[A](a: IndexedSeq[A], shift: Int): Unit =
    rotate(a, 0, a.size, shift);
  def rotate[A](a: IndexedSeq[A], start: Int, end: Int, shift: Int): Unit = {
    val len = end - start;
    if (len == 0)
      return;

    var s = shift % len;
    if (shift == 0)
      return;
    if (s < 0)
      s = len + s;

    val c = gcd(len, s);
    var i = 0;
    while (i < c) {
      var k = i;
      var x = a(start + len - s + k);
      do {
        x = swap(a, start + k, x);
        k = (k + s) % len;
      } while (k != i);
      i = i + 1;
    }
    return;
  }

答案 3 :(得分:4)

我解决Scala问题的方法是首先在Haskell中解决它们,然后进行翻译。 :)

reorderings xs = take len . map (take len) . tails . cycle $ xs
  where len = length xs

这是我能想到的最简单的方法,通过“左移”反复生成所有可能的班次列表。

ghci> reorderings [1..5]
[[1,2,3,4,5],[2,3,4,5,1],[3,4,5,1,2],[4,5,1,2,3],[5,1,2,3,4]]

这个概念相对简单(对于那些熟悉函数式编程的人来说)。首先,cycle原始列表,产生一个从中抽取的无限流。接下来,将该流中断为流的流,其中每个后续流已丢弃前一流的第一个元素(tails)。接下来,将每个子流限制为原始列表的长度(map (take len))。最后,将流的流限制为原始列表的长度,因为只有len可能的重新排序(take len)。

现在让我们在Scala中做到这一点。

def reorderings[A](xs: List[A]):List[List[A]] = {
  val len = xs.length
  Stream.continually(xs).flatten // cycle
    .tails
    .map(_.take(len).toList)
    .take(len)
    .toList
}

我们只需要为cycle使用一个小的解决方法(不确定Scala标准库是否提供循环,但我很惊讶地发现它们提供了tails),以及一些toList } s(Haskell列出懒惰流,而Scala是严格的),但除此之外,它与Haskell完全相同,据我所知,行为完全相同。您几乎可以认为Scala的.表现得像Haskell一样,除了以相反的方式流动。

另请注意,这与dhg的解决方案几乎相同,除非没有反转,其中(在上行方向)使其更有效,但(在下行方向)提供“回绕”顺序的循环,而不是“前进” “订单。

答案 4 :(得分:4)

@ dhg和@Roman Zykov版本的精彩组合:

scala> val l = List(1,2,3,4,5)
l: List[Int] = List(1, 2, 3, 4, 5)

scala> val re = Stream continually (l ++ l.init sliding l.length) flatten
re: scala.collection.immutable.Stream[List[Int]] = Stream(List(1, 2, 3, 4, 5), ?)

scala> re take 10 foreach println
List(1, 2, 3, 4, 5)
List(2, 3, 4, 5, 1)
List(3, 4, 5, 1, 2)
List(4, 5, 1, 2, 3)
List(5, 1, 2, 3, 4)
List(1, 2, 3, 4, 5)
List(2, 3, 4, 5, 1)
List(3, 4, 5, 1, 2)
List(4, 5, 1, 2, 3)
List(5, 1, 2, 3, 4)

答案 5 :(得分:3)

有一个非常简单的解决方案:

val orderings = List(1,2,3,4,5)
(orderings ++ orderings.dropRight(1)).sliding(orderings.length).toList

List(List(1, 2, 3, 4, 5), List(2, 3, 4, 5, 1), List(3, 4, 5, 1, 2), List(4, 5, 1, 2, 3), List(5, 1, 2, 3, 4))

答案 6 :(得分:2)

我接受它:

{{1}}

答案 7 :(得分:1)

以下是序列的一种可能解决方案

class ShiftWarper( seq: Seq[ Int ] ) {
  def shiftLeft: Seq[ Int ] = {
    if ( seq.isEmpty ) {
      seq
    } else {
      seq.tail :+ seq.head
    }
  }
  def shiftRight: Seq[ Int ] = {
    if ( seq.isEmpty ) {
      seq
    } else {
      seq.last +: seq.init
    }
  }
}
implicit def createShiftWarper( seq: Seq[ Int ] ) =
    new ShiftWarper( seq ) 

def shift_n_Times(
  times: Int,
  seq: Seq[ Int ],
  operation: Seq[ Int ] => Seq[ Int ] ): Seq[ Int ] = {
  if ( times > 0 ) {
    shift_n_Times(
      times - 1,
      operation( seq ),
      operation )
  } else {
    seq
  }
} 

val initialSeq = ( 0 to 9 )

( initialSeq shiftLeft ) shiftRight
shift_n_Times(
  5,
  initialSeq,
  initialSeq => new ShiftWarper( initialSeq ).shiftRight )

答案 8 :(得分:1)

这是另一个简单的scala解决方案,用于将Stream向右或向左移动任意数量。 “循环”无条件地重复流,然后“移位”找到正确的切片。 “posMod”允许你移动一个大于xs.length的索引,但实际上没有超过无限流中的xs.length元素:

str.splitKeep(['.', ',', '?', ';', '\n'])

然后:

scala> def posMod(a:Int, b:Int) = (a % b + b) % b

scala> def cycle[T](xs : Stream[T]) : Stream[T] = xs #::: cycle(xs)

scala> def shift[T](xs:Stream[T], x: Int) = cycle(xs)
          .drop(posMod(x, xs.length))
          .take(xs.length)

答案 9 :(得分:1)

我的主张:

def circ[A]( L: List[A], times: Int ): List[A] = {
    if ( times == 0 || L.size < 2 ) L
    else circ(L.drop(1) :+ L.head , times-1)
}

val G = (1 to 10).toList
println( circ(G,1) ) //List(2, 3, 4, 5, 6, 7, 8, 9, 10, 1)
println( circ(G,2) ) //List(3, 4, 5, 6, 7, 8, 9, 10, 1, 2)
println( circ(G,3) ) //List(4, 5, 6, 7, 8, 9, 10, 1, 2, 3)
println( circ(G,4) ) //List(5, 6, 7, 8, 9, 10, 1, 2, 3, 4)

答案 10 :(得分:1)

您可以实例化一个包含数组(A)的函数和您需要的旋转步数(S):

<tr>
    <td class="Shorter" style="width:29%;">
    <a id="WorkflowContentPlaceHolderBody_wzWorkflowConfig_gvCol1Step2_lnkControlLabel_10" href="javascript:__doPostBack('ctl00$WorkflowContentPlaceHolderBody$wzWorkflowConfig$gvCol1Step2$ctl12$lnkControlLabel','')">TextBox_Col1_201609150233466412</a>
    </td><td style="width:30%;">
    <input name="ctl00$WorkflowContentPlaceHolderBody$wzWorkflowConfig$gvCol1Step2$ctl12$txt" type="text" id="WorkflowContentPlaceHolderBody_wzWorkflowConfig_gvCol1Step2_txt_10" style="width:90%;">
    </td>
    </tr>