ashawley
ashawley

Reputation: 4283

Drop first element conditionally in Scala?

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

Answers (6)

Antonio Cortés
Antonio Cortés

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

vishnuvr
vishnuvr

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

The Archetypal Paul
The Archetypal Paul

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

Randomness Slayer
Randomness Slayer

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

PH88
PH88

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

λ Allquantor λ
λ Allquantor λ

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

Related Questions