删除给定列表中给定数量的正项

时间:2017-04-28 15:27:46

标签: scala list scala-collections

假设我需要一个函数List[Int] => Option[List[Int]]来删除给定列表的确切n元素,当且仅当所有元素> 0时。如果列表大小<= n,该函数应返回None

例如:

def posn(n: Int): List[Int] => Option[List[Int]] = ???
val pos4: List[Int] => Option[List[Int]] = posn(4)

scala> pos4(Nil)
res18: Option[List[Int]] = None

scala> pos4(List(-1))
res19: Option[List[Int]] = None

scala> pos4(List(-1, 2, 3))
res20: Option[List[Int]] = None

scala> pos4(List(1, 2, 3))
res21: Option[List[Int]] = None

scala> pos4(List(1, 2, 3, 4, 5))
res22: Option[List[Int]] = Some(List(5))

scala> pos4(List(1, 2, 3, -4, 5))
res23: Option[List[Int]] = None

我正在写posn

def posn(n: Int): List[Int] => Option[List[Int]] = xs => 
  if (xs.size >= n && xs.take(n).forall(_ > 0)) Some(xs.drop(n)) else None

这个功能似乎工作有点看起来不优雅和惯用。你会怎么重写呢?

4 个答案:

答案 0 :(得分:4)

这是一个(可以说)使用模式匹配和对posn的递归调用的更惯用的实现 - 但我不确定它是否比你建议的实现更可取:

def posn(n: Int): List[Int] => Option[List[Int]] = xs => (n, xs) match {
  case (0, _) => Some(xs) // stop if enough objects dropped
  case (_, head :: tail) if head > 0 => posn(n - 1)(tail) // drop positive and move on
  case _ => None // found a negative item or end of xs => "fail"
}

答案 1 :(得分:2)

我不知道是否有惯用或优雅的方式来做到这一点。似乎没有可以从您的逻辑中提取的通用模式,除了您已经完成的工作(使用droptake),所以我不相信您会发现更有用的预定义方法

但是,您正在遍历您的列表几次,这可以避免:

def posn(n: Int): List[Int] => Option[List[Int]] = xs => {
  val (head, tail) = xs.splitAt(n) //does take and drop in one run
  if (head.lengthCompare(n) == 0 && head.forall(_ > 0)) Some(tail) // lengthCompare does not compute the whole length if there is no need to
  else None 
}

这仍然不完美,比你的版本更冗长。

您也可以使用尾递归(此处假设为n>=0)一次完成所有操作:

def posn(n: Int): List[Int] => Option[List[Int]] = xs =>
  if (n == 0) Some(xs) 
  else if (xs.isEmpty || xs.head <= 0) None
  else posn(n - 1)(xs.tail)

如果List被天真地实施,这会更有效率,但我真的怀疑你会看到任何进步。

答案 2 :(得分:2)

我会编写一个通用版本并使用它来定义posn

def dropWhen[T](n: Int, p: T => Boolean, l: List[T]): Option[List[T]] = {
    val (f, s) = l.splitAt(n)
    if (f.length >= n && f.forall(p)) { Some(s) } else { None }
}

def posn(n: Int): List[Int] => Option[List[Int]] = l => dropWhen(n, (i : Int) => i > 0, l)

请注意,此方法会扫描长度n的前缀两次

答案 3 :(得分:1)

另一种(非递归)替代方法:使用zipWithIndexdropWhile删除前N个正数,然后选中head以查看第一个剩余项是否正好位于n:如果是,我们得到了我们想要的,否则我们可以返回None

def posn(n: Int): List[Int] => Option[List[Int]] = xs =>
  Some(xs.zipWithIndex.dropWhile { case (v, i) => v > 0 && i < n })
    .find(_.headOption.exists(_._2 == n)) // first index should be n 
    .map(_.map(_._1)) // remove indices