如何使用函数式编程返回列表中的所有正数和第一个负数?

jbr*_*own 15 functional-programming scala

想象一下,我有一个正面和负面的未排序列表.我想返回一个包含所有正整数和第一个负数的列表,但是然后忽略列表中的所有后续负数,同时保留排序.

我无法做到:

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]
Run Code Online (Sandbox Code Playgroud)

我怎么能用Scala中的FP做到这一点?我在想(可能不会编译......):

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?
Run Code Online (Sandbox Code Playgroud)

这是正确的方法,还是有更优雅,简洁的方式?

Dav*_*ith 10

如果没有稍微过于聪明的递归+模式匹配答案,那就不是一个合适的函数式编程问题.

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
   }
}
Run Code Online (Sandbox Code Playgroud)


Dim*_*ima 7

如果您不介意保持一点状态,您可以一次性完成 - 请注意var neg:

var neg = false
list.filter { 
    case x if x > 0  => true
    case _ if !neg   => neg = true
}
Run Code Online (Sandbox Code Playgroud)


zun*_*der 7

这是一种尾递归方式.与mz的答案相比,它只迭代你的列表一次,与Dimas的答案相比,它不使用可变状态,所以它是纯粹的功能.

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)
Run Code Online (Sandbox Code Playgroud)

编辑:

上述实现提供了相反的结果.为了保留订单,您可以执行以下操作:

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)
Run Code Online (Sandbox Code Playgroud)

  • `out:+ head` O(N),整体为O(N ^ 2).最简单的方法是在你的第一个版本的结果上调用`.reverse` (4认同)
  • 它并不像我希望的那样简洁,但它具有高性能和纯粹的功能,对我来说是最好的解决方案,谢谢! (2认同)

The*_*aul 6

需求的直接翻译非常清楚,在列表上进行一次传递,并且功能正常:

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)
Run Code Online (Sandbox Code Playgroud)

(List周围firstneg.head只是:::两边的对称)