Reputation: 4283
Trying to remove the first element of a list if it is zero (not really, but for the purpose of an example).
Given a list:
val ns = List(0, 1, 2)
Deleting the first zero could be done by dropping the first matches for zero:
List(0, 1, 2).dropWhile(_ == 0)
res1: List[Int] = List(1, 2)
Or you could delete everything that's not a zero.
List(0, 1, 2).filter(_ > 0)
res2: List[Int] = List(1, 2)
The problem with these is when the list has multiple zeroes. The previous solutions don't work, because they delete too many zeroes:
List(0, 0, 1, 2, 0).filter(_ > 0)
res3: List[Int] = List(1, 2)
List(0, 0, 1, 2, 0).dropWhile(_ == 0)
res4: List[Int] = List(1, 2, 0)
Is there an existing function for this?
Upvotes: 3
Views: 1823
Reputation: 1
That's a good option if you wanna drop the first element according to the filter but the value isn't in the first place.
def dropFirstElement( seq: Seq[Int], filterValue: Int ): Seq[Int] = {
seq.headOption match {
case None => seq
case Some( `filterValue` ) => seq.tail
case Some( value ) => value +: dropFirstElement( seq.tail, filterValue )
}
}
Upvotes: 0
Reputation: 29
You can zip the list with an index:
ns.zipWithIndex.filter( x =>( x._1 != 0 || x._2 != 0)).map(_._1)
Here's a similar solution using dropWhile
:
ns.zipWithIndex.dropWhile {
case (x, idx) => x == 0 && idx == 0
} map(_._1)
This could also be a for-comprehension
for {
(x, idx) <- ns.zipWithIndex
if (x != 0 || idx != 0) )
} yield {
x
}
But as Paul mentioned, it will unnecessarily iterate over the entire list.
Upvotes: 1
Reputation: 41759
Here's a generalised variant (drop up to K elements matching the predicate) which does not process the rest of the list
def dropWhileLimit[A](xs: List[A], f: A => Boolean, k: Int): List[A] = {
if (k <= 0 || xs.isEmpty || !f(xs.head)) xs
else dropWhileLimit(xs.tail, f, k - 1)
}
and some test cases:
dropWhileLimit(List(0,1,2,3,4), { x:Int => x == 0}, 1)
//> res0: List[Int] = List(1, 2, 3, 4)
dropWhileLimit(List(0,1,2,3,4), { x:Int => x == 0}, 2)
//> res1: List[Int] = List(1, 2, 3, 4)
dropWhileLimit(List(0,0,0,0,0), { x:Int => x == 0}, 1)
//> res2: List[Int] = List(0, 0, 0, 0)
dropWhileLimit(List(0,0,0,0,0), { x:Int => x == 0}, 3)
//> res3: List[Int] = List(0, 0)
Upvotes: 1
Reputation: 724
If you only want to drop the first element conditionally, then as jwvh commented, an if/else
comprehension is probably the simplest:
if (ns.nonEmpty && ns.head == 0) {
ns.tail
} else {
ns
}
You could then of course wrap this into a function.
You could look for a sequence of one zero, and drop it:
if (ns.startsWith(List(0))) {
ns.drop(1)
} else {
ns
}
Also known as returning the tail:
if (ns.startsWith(List(0))) {
ns.tail
} else {
ns
}
Upvotes: 2
Reputation: 1806
I also think pattern matching is the best option for readability and performance (I tested and the pattern matching code from OP actually performs better than simple if ... else ...
.).
List(0, 0, 1, 2, 0) match {
case 0 :: xs => xs
case xs => xs
}
res10: List[Int] = List(0, 1, 2, 0)
And, no, there's no simple built in function for this.
Upvotes: 6
Reputation: 1131
A neat generalized solution would be explicitly add information to your elements.
Example: How to drop by condition and limit the amount from left to right?
List(0,0,0,1,2,2,3).zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2})
Result:
res0: List[(Int, Int)] = List((0,2), (1,3), (2,4), (2,5), (3,6))
You can get your previous representation with:
res0.map.{_._1}
To do everything in N, you can use lazy evaluation + the force
method.
List(0,0,0,1,2,2,3).view.zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2}).map {_._1}.force
This will basically do all the operations on your initial collection in one single iteration. See scaladoc for more info to Scala views..
Modifying your condition on the right size you can choose how far the drop condition will reach inside your collection.
Upvotes: 1