假设我有一个case class A(id: Int, str: String)
的列表和一个A
的实例。我需要用新实例替换列表中的项目,或者将新实例追加到列表中。
case class A(id: Int, str: String)
def replaceOrAppend(as: List[A], a: A): List[A] = ???
val as = List(A(1, "a1"), A(2, "a2"), A(3, "a3"))
replaceOrAppend(as, A(2, "xyz")) // List(A(1, "a1"), A(2, "xyz"), A(3, "a3"))
replaceOrAppend(as, A(5, "xyz")) // List(A(1, "a1"), A(2, "a2"), A(3, "a3"), A(5, "xyz"))
我可以这样写replaceOrAppend
:
def replaceOrAppend(as: List[A], a: A): List[A] =
if (as.exists(_.id == a.id)) as.map(x => if (x.id == a.id) a else x) else as :+ a
此实现有点笨拙,显然次优,因为它两次通过了输入列表。如何实现replaceOrAppend
只传递一次输入列表?
答案 0 :(得分:2)
如果命令不是必需的,我会选择:
def replaceOrAppend(as: List[A], a: A): List[A] =
a::as.filterNot(_.id == a.id)
如果订单与id
或str
相关,这也将起作用:
def replaceOrAppend(as: List[A], a: A): List[A] =
(a::as.filterNot(_.id == a.id)).sortBy(_.id)
如果必须保留订单(如米歇尔建议-我找不到更好的东西了):
def replaceOrAppend(as: List[A], a: A): List[A] =
as.span(_.id != a.id) match { case (xs, ys) => xs ++ (a :: ys.drop(1)) }
答案 1 :(得分:1)
这里是另一个:
def replaceOrAppend(as: List[A], a: A): List[A] = {
as.find(_.id==a.id).map(op => {
as.map(el => el match {
case e if e.id==a.id => e.copy(str=a.str)
case _ => el
})
}).getOrElse((a::as.reverse).reverse)
}
答案 2 :(得分:1)
那呢?仍然笨拙,但仅使用一次迭代。
def replaceOrAppend(as: List[A], a: A): List[A] = {
val (updatedList,itemToAppend) = as.foldLeft((List[A](),Option(a))) {
case ((acc, Some(item)), l) =>
if (item.id == l.id) (acc :+ item, None)
else (acc :+ l, Some(item))
case ((acc, None), l) => (acc :+ l, None)
}
itemToAppend match {
case Some(item) => updatedList :+ item
case None => updatedList
}
}
答案 3 :(得分:1)
我不明白为什么人们会忘记处理功能列表的最佳方法是通过模式匹配 + tail-recursion 。
恕我直言,这看起来更干净,并试图尽可能地提高效率。
final case class A(id: Int, str: String)
def replaceOrAppend(as: List[A], a: A): List[A] = {
@annotation.tailrec
def loop(remaining: List[A], acc: List[A]): List[A] =
remaining match {
case x :: xs if (x.id == a.id) =>
acc reverse_::: (a :: xs)
case x :: xs =>
loop(remaining = xs, acc = x :: acc)
case Nil =>
(a :: acc).reverse
}
loop(remaining = as, acc = List.empty)
}
从技术上讲,这在最坏的情况下会两次遍历列表。
但是,始终要从头开始并在末尾倒序来构建列表,而不是进行大量附加操作。