是否有条件地删除Scala中的第一个元素?

是否有条件地删除Scala中的第一个元素?,scala,scala-collections,Scala,Scala Collections,如果列表的第一个元素为零,则尝试删除它(不是真的,但出于示例目的) 给出一个列表: 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) 问题在于列表中有多个零。以前的解决方案不

如果列表的第一个元素为零,则尝试删除它(不是真的,但出于示例目的)

给出一个列表:

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
}
当然,您可以将其包装成一个函数

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

if (ns.startsWith(List(0))) {
  ns.drop(1)
} else {
  ns
}
也称为返回尾部:

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

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

例如: 如何按条件下降并从左到右限制数量

您可以通过以下方式获得以前的陈述:

res0.map.{_._1}
要在N中执行所有操作,可以使用lazy求值+
force
方法

List(0,0,0,1,2,2,3).view.zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2}).map {_._1}.force
List(0,0,0,1,2,2,3).view.zipWithIndex.dropWhile({case(elem,index)=>elem==0&&index<2}).map{uu.\1}.force
这将基本上在一次迭代中完成初始集合上的所有操作。有关Scala视图的更多信息,请参阅


将条件修改为正确的大小,您可以选择放置条件在集合中的范围

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

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
}
用于{

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


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

这里有一个通用的变体(最多可下拉到与谓词匹配的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)
  } 

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

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

@ashawley,您可以测试空性
if(ns.nonEmpty&&(ns.head==0))…
或使用
headOption
if(ns.headOption==Some(0))…
。但是我还是喜欢你的模式匹配方法。我认为如果…else是最有效的。但是如果你想把它复杂化,用一个表达式来实现,你可以做一些类似于
list.headOption.filter(!=0).fold(list.empty[Int])(list())::list.tail的事情。答案似乎是编写这个函数的方法(与OP自身的变体相比,有几个更慢、更不清晰和/或更复杂)。很明显,OP很有能力对其进行编码。实际问题是“是否存在此功能?”。答案是“否”这是对OP的建议的改进吗?我认为任何使用
zipWithIndex
只是在稍后测试索引为零的解决方案都没有改进。除此之外,他们迭代整个列表(两次-一次添加索引,一次删除索引)对于应该是恒定时间的操作。@TheArchetypalPaul您可以通过使用
view.zipWithIndex…force
来避免这种情况。不行。
force
将计算除第一个元素(如果已删除)或全部(如果未删除)之外的所有元素,因此仍然是O(N)。而使用
视图
,则会增加开销。OP的要求是检查列表的第一个元素并有条件地将其删除。在我看来,任何无意义地处理列表中所有其他元素的解决方案都不是一种改进。模式匹配选项是一种方法。您的“懒惰”版本是O(列表长度)不过,没有必要这样做。它应该是O(前缀到--test的长度)。
List(0, 0, 1, 2, 0) match { 
  case 0 :: xs => xs 
  case xs => xs
}
res10: List[Int] = List(0, 1, 2, 0)
  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 )
  }
}