jbrown
jbrown

Reputation: 7996

How to return all positives and the first negative number in a list using functional programming?

Imagine I have an unsorted list of positive & negative ints. I want to return a list containing all the positive ints, and the first negative number, but then ignore all subsequent negative numbers from the list, while preserving the ordering.

Imperatively I could do:

l = [1, 2, -4, 5, -6, -1, 3]
out = []
first = true
for n in l:
    if n >= 0:
        out.push(n)
    else if first:
        out.push(n)
        first = false

// out = [1, 2, -4, 5, 3]

How could I do this with FP in Scala? I was thinking (probably won't compile...):

val l = List(1, 2, -4, 5, -6, -1, 3)
val posl = l.map(_ >= 0)
val negl = l.zipWithIndex.map((n, i) => if (n < 0) (i, n) else (None, None)).head
// now split posl at negl._1, and create a new list of leftSlice :: negl._2 :: rightSlice?

Is this the right approach, or is there a more elegant, succinct way?

Upvotes: 15

Views: 2895

Answers (11)

Lionel Parreaux
Lionel Parreaux

Reputation: 1225

Here is an improvement on the most concise answer I saw, by @sschaef in a comment:

val l = List(1, 2, -4, 5, -6, -1, 3)

l.span(_>=0) match { case (left, Nil)      => left
                     case (left, x::right) => left ++ (x +: right.filter(_>=0)) }

Upvotes: 2

Dima
Dima

Reputation: 40500

You can do it in one pass provided you don't mind keeping a bit of state around - mind the var neg:

var neg = false
list.filter { 
    case x if x > 0  => true
    case _ if !neg   => neg = true
}

Upvotes: 7

Rex Kerr
Rex Kerr

Reputation: 167901

There are a lot of tail-recursive solutions, but they're all longer than they need to be.

def oneNeg(xs: List[Int]): List[Int] = {
  def loop(in: List[Int], out: List[Int], neg: Int): List[Int] = in match {
    case Nil => out
    case x :: rest =>
      if (neg < 0 && x < 0) loop(rest, out, neg)
      else loop(rest, x :: out, x min neg)
  }
  loop(xs, Nil, 0).reverse
}

If this is not a public-facing API, you can make it shorter yet by just exposing the inner method alone:

def oneNeg(in: List[Int], out: List[Int] = Nil, neg: Int = 0): List[Int] = 
  in match {
    case Nil => out.reverse
    case x :: rest =>
      if (neg < 0 && x < 0) oneNeg(rest, out, neg)
      else oneNeg(rest, x :: out, x min neg)
  }

Upvotes: 2

The Archetypal Paul
The Archetypal Paul

Reputation: 41769

A direct translation of the requirement is pretty clear, takes one pass over the list, and is functional:

val l = List(1, 2, -4, 5, -6, -1, 3) 

// split into any initial positive numbers, and the rest of the list 
val (pos, firstneg) = l.span(_ >= 0)

// if there were no negative numbers, return the original list.
// otherwise, it's the initial positives, the first negative, and
// the positive numbers from the rest of the list.
if (firstNeg.isEmpty) l else pos:::List(firstneg.head):::firstneg.tail.filter(_>=0)
//> res0: List[Int] = List(1, 2, -4, 5, 3)

(The List around firstneg.head is just for the symmetry of ::: both sides)

Upvotes: 6

Dave Griffith
Dave Griffith

Reputation: 20515

It wouldn't be a proper functional programming question without a slightly-too-clever recursion+pattern matching answer.

def firstNegAllPos(l:List[Int]):List[Int] = {
   l match{
      case x::xs if x>=0 => x::firstNegAllPos(xs)
      case x::xs if x<0 => x::xs.filter(_>=0)
      case Nil => Nil
   }
}

Upvotes: 10

nitishagar
nitishagar

Reputation: 9413

You can try:

val list = List(1, 2, -4, 5, -6, -1, 3)
val index = list.indexWhere(_ < 0) + 1
list.take(index) ++ list.drop(index).filter(_ > 0)

Upvotes: 1

Vladimir Matveev
Vladimir Matveev

Reputation: 127871

This is an obvious work for fold operation!

val l = List(1, 2, -4, 5, -6, -1, 3)

var result = l.foldLeft((true, Vector.empty[Int])) {
  case ((f, r), x) if x >= 0 => (f, r :+ x)
  case ((f, r), x) if f => (false, r :+ x)
  case ((f, r), x) => (f, r)
}._2

println(result)  // Vector(1, 2, -4, 5, 3)

I used a vector as an intermediate structure; you can convert it to a list with toList, if you need it. Or you can use it instead of the Vector, but you will have to reverse the addition order (r :+ x => x :: r) and then reverse the list in the end with reverse method.

Upvotes: 4

roustem
roustem

Reputation: 447

val l = List(1, 2, -4, 5, -6, -1, 3) 
val (left, right) = l.span(_ > 0)
val result = right.headOption match {
    case Some(n) => (left :+ n) ++ right.tail.filter(_ > 0)
    case None => left
}

Upvotes: 4

Alexander Weber
Alexander Weber

Reputation: 799

Here is a tail-recursive way. Compared to m-z's answer, it iterates your list only one time, compared to Dimas answer, it does not use mutable state, so it is pure functional.

def firstNegAllPos(list: List[Int]) : List[Int] = {
  def loop(in: List[Int], out: List[Int], negfound: Boolean) : List [Int] = {
    in match {
      case Nil => out
      case head :: tail =>
        if (negfound)
          loop(tail, if (head < 0) out else head :: out, true)
        else
          loop(tail, head :: out, head < 0)
    }
  }
  loop(list, Nil, false)
}

firstNegAllPos(List(1, 2, -4, 5, -6, -1, 3)) // List(3, 5, -4, 2, 1)

Edit:

The above implementation provides a reversed result. In order to preserve order you can do following:

def firstNegAllPos(list: List[Int]) : List[Int] = {
  def loop(in: List[Int], out: List[Int], negfound: Boolean) : List [Int] = {
    in match {
      case Nil => out
      case head :: tail =>
        if (negfound)
          loop(tail, if (head < 0) out else head :: out, true)
        else
          loop(tail, head :: out, head < 0)
    }
  }
  loop(list, Nil, false).reverse
}

firstNegAllPos(List(1, 2, -4, 5, -6, -1, 3)) // List(1, 2, -4, 5, 3)

Upvotes: 7

user5102379
user5102379

Reputation: 1512

Here is tail recursive solution preserving order:

  def posNeg(xs: List[Int]): List[Int] = {
    @tailrec
    def go(tail: List[Int], res: List[Int]): List[Int] = {
      tail match {
        case h :: t =>
          if (h >= 0) go(t, h :: res)
          else (h :: res).reverse ::: t.filter(_ > 0)
        case _ => res
      }
    }
    go(xs, Nil)
  }

Upvotes: 2

Michael Zajac
Michael Zajac

Reputation: 55569

If you want to maintain the order (i.e., the position of the negative), you could do something like this:

val list = List(1, 2, -4, 5, -6, -1, 3)
val negIndex = list.indexWhere(_ < 0)
val positive = list.zipWithIndex.filter { case (num, index) =>
    num >= 0 || index == negIndex
}.map(_._1)

The negative requirement makes it hard to keep it more succinct than that. My strategy is to just grab the index of the first negative with indexWhere (will be -1 if there is none), then filter all the negatives out of the list, except for the index of the first negative. If the index was -1, no harm, no foul.

Upvotes: 2

Related Questions