5

I need to shuffle part of an ArrayBuffer, preferably in-place so no copies are required. For example, if an ArrayBuffer has 10 elements, and I want to shuffle elements 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

I've written something like shown below, but it requires copies and iterating over loops a couple of times. It seems like there should be a more efficient way of doing this.

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

I could find nothing about partially shuffling an ArrayBuffer with Google. I assume I will have to write my own method, but in doing so I would like to prevent copies.

Nic Foster
  • 2,864
  • 1
  • 27
  • 45

2 Answers2

5

If you can use a subtype of ArrayBuffer you can access the underlying array directly, since ResizableArray has a protected member 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)

Notes:

  • The upper bound for java.util.List#subList is exclusive
  • It's reasonably efficient because Arrays#asList doesn't create a new set of elements: it's backed by the array itself (hence no add or remove methods)
  • If using for real, you probably want to add bounds-checks on a and b
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
3

I'm not entirely sure why it must be in place - you might want to think that over. But, assuming it's the right thing to do, this could do it:

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

You can use it like:

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

edit: If you're willing to pay the extra cost of an intermediate sequence, this is more reasonable, algorithmically speaking:

  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
  }
}
Derek Wyatt
  • 2,697
  • 15
  • 23
  • When I use what you have I get this: `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)`. It appears to not actually shuffle anything. – Nic Foster Mar 05 '12 at 19:43
  • Try it more than once. Because it's shuffling in place, it doesn't work the way you think (it's really simple). It doesn't exchange `t._1` with `t._2` all the time since it moves things as it goes and some of the things it moves have already been moved. Feel free to improve it. – Derek Wyatt Mar 05 '12 at 19:47
  • I see, yes it did shuffle after the second call to it. The only issue is that start and end both need to be decreased by one, because using shufflePart(2, 7) causes it to shuffle 3-8. Also, by using shuffle on `seq` there is still likely a copy being made right there. Maybe I'm overthinking the issue with copying. – Nic Foster Mar 05 '12 at 19:52
  • If that's the only reason, then yes... you're definitely overthinking it. Go for immutability over mutability until there's a problem with the former. I've also changed it to handle indexes properly - not the way you said :) This is a 0-based index. i.e. index '2' _should_ modify 3. My mistake was using `to` instead of `until`, which I now do. – Derek Wyatt Mar 05 '12 at 19:57
  • Note that you'll take a slight performance hit if you enrich your library with structural typing, as it uses reflection. See [this question](http://stackoverflow.com/questions/9402218/what-is-the-best-way-to-use-pimp-my-library-in-scala). – Nicolas Payette Mar 05 '12 at 21:17