Scala is very elegant in filtering immutable sequences:
var l = List(1,2,3,4,5,6)
l = l.filter(_%2==1)
But how do I do this wit开发者_如何学Ch a mutable collections like ArrayBuffer? All I found is the removal of single elements or slices, or remove elements from another sequence, but nothing that removes elements given by a predicate.
Edit: I was hoping to find something similar tho this:
trait Removable[A] extends Buffer[A]{
def removeIf(p: A => Boolean){
var it1 = 0
var it2 = 0
while(it2 < length){
if( p( this(it2) ) ){
it2 += 1;
}
else {
this(it1) = this(it2)
it1 += 1;
it2 += 1;
}
}
trimEnd(it2-it1)
}
}
this filters in linear time and can be mixed into any Buffer, but only ArrayBuffer makes sense, on ListBuffers it would be slow, because indexing does take linear time.
My guess is that it's more efficient to filter by building a new buffer, so you would normally just use filter
and use its result. Otherwise you can write your own in-place filter method:
def filterInPlace[A](b: collection.mutable.Buffer[A])(fun: A => Boolean): Unit = {
var sz = b.size
var i = 0; while(i < sz) {
if (fun(b(i))) {
i += 1
} else {
sz -= 1
b.remove(i)
}
}
}
val b = collection.mutable.ArrayBuffer((1 to 6): _ *)
filterInPlace(b)(_ % 2 == 1)
println(b)
You do it the same with ArrayBuffer
. All collection classes have the same methods available.
There has been discussion about having a set of methods that work by performing mutation, but coming up with a good, generic set is surprisingly hard, and, on the other hand, there just hasn't been enough demand for it.
This worked for me, but only with clone(), thus still making a new ArrayBuffer :-)
scala> import collection.mutable.ArrayBuffer
import collection.mutable.ArrayBuffer
scala> val buf = ArrayBuffer(1,2,3,4,5,6,7,8,9,10)
buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> buf.clone foreach { x => if (x > 4) buf -= x }
scala> buf
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4)
But better way would be to make a new array of only those elements, which you want to remove (thus not copying the whole buffer) and then remove them:
scala> val buf = ArrayBuffer(1,2,3,4,5,6,7,8,9,10)
buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> buf filter { _ > 4 } foreach { buf -= _ }
scala> buf
res3: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4)
I came up with this
import scala.collection.mutable
trait BufferUtils {
import BufferUtils._
implicit def extendMutableBuffer[T](org: mutable.Buffer[T]): ExtendedBuffer[T] = new ExtendedBuffer(org)
}
object BufferUtils extends BufferUtils {
implicit class ExtendedBuffer[T](val org: mutable.Buffer[T]) extends AnyVal {
def removeIf(pred: (T) => Boolean): Unit = {
// target holds the index we want to move the next element to
var target = 0
for (i <- org.indices;
elem = org(i)
if !pred(elem)) {
org(target) = elem
target += 1
}
org.remove(target, org.size - target)
}
}
}
Often withFilter
is good enough, especially if the Buffer is converted to an immutable structure in the end. True, it doesn't really remove the elements, but at least it doesn't create a new ArrayBuffer object.
精彩评论