假设我想编写一个函数foo
,它迭代一个列表List[A]
,并在每个元素上调用一些函数f
。该函数返回Option[B]
。如果遇到None
,我希望整个函数foo
返回None
。如果没有,我希望它返回整个结果列表List[B]
。
我可以这样写:
def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
var res: List[B] = Nil
for (element <- list) {
f(element) match {
case Some(fRes) => res = fRes :: res
case _ => return None
}
}
Some(res.reverse)
}
但是这个代码看起来很难看,因为我必须使用var
和return
。有没有办法让它看起来更好?
修改
在这个问题中我假设f
是一个非常耗时的功能,如果我们已经知道结果应该是None
答案 0 :(得分:3)
可能有几种不同的方法可以做到这一点;这是我能想到的那么简单:
def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
val x = list.map(f)
if (x.contains(None)) {
None
} else {
Some(x.flatten)
}
}
如果遇到None
,你确实会失去短路的好处。您可以通过转换为Stream
然后返回来解决这个问题,即val x = list.toStream.map(f)
和Some(x.flatten.toList)
答案 1 :(得分:1)
这样的事情:
def foo[A, B](list: List[A], f: (A) => Option[B]): Option[List[B]] = {
val lst = list.view.map(f)
if(lst.exists(_.isEmpty)) None
else Option { lst.flatten.to[List] }
}
答案 2 :(得分:0)
我会写如下 -
def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
val newList = list.map(x => f(x))
if(newList.contains(None)) None else Some(newList.flatten)
}
如果B是简单类型,如Int,Double等,但不是像List这样的集合,那么下面的内容非常简单 -
def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
val newList = list.flatMap(f)
if(newList.size < list.size) None else Some(newList)
}
答案 3 :(得分:0)
使用Stream
的另一种选择(我认为这是一个很酷的主意)是使用一个允许衬衫电路的显式尾递归:
def foo[A, B](list: List[A])(f: (A) => Option[B]): Option[List[B]] = {
@tailrec
def impl(list: List[A], acc: List[B]): Option[List[B]] = list match {
case a :: rest => f(a) match {
case Some(b) => impl(rest, b :: acc)
case None => None
}
case _ => Some(acc.reverse)
}
impl(list, List.empty)
}
这显然比基于Stream
的方法更多的代码,但我希望在某些数据上它可能更快,但可能没有那么快(在所有 - Some
好的情况下)与代码一样因map
强制执行acc.reverse
而导致简单List
。
答案 4 :(得分:0)
这种事情通常是递归地完成的:
@tailrec
def foo(
list: List[A],
result: List[B] = Nil
)(f: A => Option[B]): Option[List[B]] = list match {
case Nil => Some(result.reverse)
case head :: tail => f(head) match {
case Some(b) => foo(tail, b::result)(f)
case None => None
}
}
您还可以将最后case
写为
case head::tail => f(head).flatMap { foo(tail, _ :: result)(f) }
但是我想,这会杀死尾递归...