在 Scala 中有条件地删除第一个元素?

Drop first element conditionally in Scala?

尝试删除列表的第一个元素(如果它为零)(不是真的,但为了举例)。

给定一个列表:

val ns = List(0, 1, 2)

删除第一个零可以通过删除零的第一个匹配来完成:

List(0, 1, 2).dropWhile(_ == 0)
res1: List[Int] = List(1, 2)

或者您可以删除非零的所有内容。

List(0, 1, 2).filter(_ > 0)
res2: List[Int] = List(1, 2)

这些问题是当列表有多个零时。以前的解决方案不起作用,因为它们删除了太多的零:

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)

这个有现成的功能吗?

如果您只想有条件地删除第一个元素,那么正如 jwvh 评论的那样,if/else 理解可能是最简单的:

if (ns.nonEmpty && ns.head == 0) {
    ns.tail
} else {
    ns
}

你当然可以把它包装成一个函数。

您可以查找一个零的序列,然后将其删除:

if (ns.startsWith(List(0))) {
  ns.drop(1)
} else {
  ns
}

也称为返回尾巴:

if (ns.startsWith(List(0))) {
  ns.tail
} else {
  ns
}

一个简洁的通用解决方案是向您的元素显式添加信息。

示例: 如何按条件掉落,从左到右限制数量?

List(0,0,0,1,2,2,3).zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2})

结果:

res0: List[(Int, Int)] = List((0,2), (1,3), (2,4), (2,5), (3,6))

您可以通过以下方式获取您之前的代表:

res0.map.{_._1}

要在 N 中做所有事情,可以使用惰性求值 + force 方法。

List(0,0,0,1,2,2,3).view.zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2}).map {_._1}.force

这基本上会在一次迭代中完成对初始 collection 的所有操作。有关 Scala 视图的更多信息,请参阅 scaladoc..

根据合适的尺寸修改您的条件,您可以选择掉落条件在您的 collection 内到达的距离。

您可以使用索引压缩列表:

ns.zipWithIndex.filter( x =>( x._1 != 0 || x._2 != 0)).map(_._1)

这是使用 dropWhile 的类似解决方案:

ns.zipWithIndex.dropWhile { 
  case (x, idx) => x == 0 && idx == 0
} map(_._1)

这也可以是一种理解

for {
  (x, idx) <- ns.zipWithIndex
  if (x != 0 || idx != 0) )
} yield {
  x
}

但正如 Paul 所提到的,它将不必要地遍历整个列表。

我还认为模式匹配是可读性和性能的最佳选择(我测试过,OP 中的模式匹配代码实际上比简单的 if ... else ... 执行得更好)。

List(0, 0, 1, 2, 0) match { 
  case 0 :: xs => xs 
  case xs => xs
}
res10: List[Int] = List(0, 1, 2, 0)

而且,不,没有用于此的简单内置函数。

这是一个通用变体(删除与谓词匹配的 K 个元素),它不处理列表的其余部分

  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)
  } 

和一些测试用例:

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)

如果您想根据过滤器删除第一个元素但值不在第一位,这是一个不错的选择。

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 )
  }
}