【问题标题】:Shuffling part of an ArrayBuffer in-place就地改组 ArrayBuffer 的一部分
【发布时间】:2012-03-23 06:21:29
【问题描述】:

我需要对 ArrayBuffer 的一部分进行洗牌,最好是就地洗牌,因此不需要副本。例如,如果一个 ArrayBuffer 有 10 个元素,我想打乱 3-7 个元素:

// Unshuffled ArrayBuffer of ints numbered 0-9
0, 1, 2, 3, 4, 5, 6, 7, 8, 9

// Region I want to shuffle is between the pipe symbols (3-7)
0, 1, 2 | 3, 4, 5, 6, 7 | 8, 9

// Example of how it might look after shuffling
0, 1, 2 | 6, 3, 5, 7, 4 | 8, 9

// Leaving us with a partially shuffled ArrayBuffer
0, 1, 2, 6, 3, 5, 7, 4, 8, 9

我已经编写了如下所示的内容,但它需要多次复制和迭代循环。似乎应该有一种更有效的方法来做到这一点。

def shufflePart(startIndex: Int, endIndex: Int) {

  val part: ArrayBuffer[Int] = ArrayBuffer[Int]()

  for (i <- startIndex to endIndex ) {
    part += this.children(i)
  }

  // Shuffle the part of the array we copied
  val shuffled = this.random.shuffle(part)
  var count: Int = 0

  // Overwrite the part of the array we chose with the shuffled version of it
  for (i <- startIndex to endIndex ) {
    this.children(i) = shuffled(count)
    count += 1
  }
}

我找不到关于使用 Google 部分改组 ArrayBuffer 的信息。我假设我必须编写自己的方法,但这样做我想防止复制。

【问题讨论】:

    标签: scala random shuffle arraybuffer


    【解决方案1】:

    如果您可以使用ArrayBuffer 的子类型,则可以直接访问底层数组,因为ResizableArray 有一个受保护的成员array

    import java.util.Collections
    import java.util.Arrays
    import collection.mutable.ArrayBuffer
    
    
    val xs = new ArrayBuffer[Int]() {
      def shuffle(a: Int, b: Int) {
        Collections.shuffle(Arrays.asList(array: _*).subList(a, b))
      }
    }
    
    xs ++= (0 to 9)    // xs = ArrayBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    xs.shuffle(3, 8)   // xs = ArrayBuffer(0, 1, 2, 4, 6, 5, 7, 3, 8, 9)
    

    注意事项:

    • java.util.List#subList 的上限是独占的
    • 它相当有效,因为Arrays#asList 不创建新的元素集:它由数组本身提供支持(因此没有添加或删除方法)
    • 如果实际使用,您可能需要在ab 上添加边界检查

    【讨论】:

      【解决方案2】:

      我不完全确定为什么它必须到位 - 您可能需要考虑一下。但是,假设这是正确的做法,这可以做到:

      import scala.collection.mutable.ArrayBuffer
      
      implicit def array2Shufflable[T](a: ArrayBuffer[T]) = new {
        def shufflePart(start: Int, end: Int) = {
          val seq = (start.max(0) until end.min(a.size - 1)).toSeq
          seq.zip(scala.util.Random.shuffle(seq)) foreach { t =>
            val x = a(t._1)
            a.update(t._1, a(t._2))
            a(t._2) = x
          }
          a
        }
      }
      

      你可以像这样使用它:

      val a = ArrayBuffer(1,2,3,4,5,6,7,8,9)
      println(a)
      println(a.shufflePart(2, 7))  
      

      编辑:如果你愿意支付中间序列的额外成本,从算法上讲,这更合理:

        def shufflePart(start: Int, end: Int) = {
          val seq = (start.max(0) until end.min(a.size - 1)).toSeq
          seq.zip(scala.util.Random.shuffle(seq) map { i =>
            a(i)
          }) foreach { t =>
            a.update(t._1, t._2)
          }
          a
        }
      }
      

      【讨论】:

      • 当我使用你拥有的东西时,我会得到这个:ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9); ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9); a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9)。它似乎并没有真正洗牌。
      • 多试几次。因为它在原地洗牌,所以它不像你想象的那样工作(它真的很简单)。它不会一直将t._1t._2 交换,因为它会随心所欲地移动东西,而且它移动的一些东西已经被移动了。随意改进它。
      • 我明白了,是的,在第二次调用它之后它确实洗牌了。唯一的问题是 start 和 end 都需要减一,因为使用 shufflePart(2, 7) 会导致它随机播放 3-8。此外,通过在seq 上使用随机播放,仍然可能会在那里制作副本。也许我在复制问题上想得太多了。
      • 如果这是唯一的原因,那么是的……你肯定想多了。追求不变性而不是可变性,直到前者出现问题。我还更改了它以正确处理索引 - 不是你说的方式:) 这是一个基于 0 的索引。即索引'2'应该修改3。我的错误是使用to而不是until,我现在这样做了。
      • 请注意,如果您使用结构类型来丰富您的库,您会受到轻微的性能影响,因为它使用反射。见this question
      猜你喜欢
      • 1970-01-01
      • 2017-01-22
      • 2014-10-29
      • 1970-01-01
      • 2019-07-15
      • 2018-09-26
      • 2011-01-17
      • 2018-07-17
      • 1970-01-01
      相关资源
      最近更新 更多