假设我需要一个函数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
这个功能似乎工作有点看起来不优雅和惯用。你会怎么重写呢?
答案 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)
我不知道是否有惯用或优雅的方式来做到这一点。似乎没有可以从您的逻辑中提取的通用模式,除了您已经完成的工作(使用drop
和take
),所以我不相信您会发现更有用的预定义方法
但是,您正在遍历您的列表几次,这可以避免:
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)
另一种(非递归)替代方法:使用zipWithIndex
和dropWhile
删除前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