问题描述
假设我有 case class A(x: Int,s: String)
并且需要使用这样的 List[A]
更新 Map[Int,String]
:
def update(as: List[A],map: Map[Int,String]): List[A] = ???
val as = List(A(1,"a"),A(2,"b"),A(3,"c"),A(4,"d"))
val map = Map(2 -> "b1",4 -> "d1",5 -> "e",6 -> "f")
update(as,map) // List(A(1,"b1"),"d1"))
我是这样写update
的:
def update(as: List[A],String]): List[A] = {
@annotation.tailrec
def loop(acc: List[A],rest: List[A],String]): List[A] = rest match {
case Nil => acc
case as => as.span(a => !map.contains(a.x)) match {
case (xs,Nil) => xs ++ acc
case (xs,y :: ys) => loop((y.copy(s = map(y.x)) +: xs) ++ acc,ys,map - y.x)
}
}
loop(Nil,as,map).reverse
}
此函数工作正常,但不是最佳的,因为当 map
为空时,它会继续迭代输入列表。此外,它看起来过于复杂。您建议如何改进此 update
函数?
解决方法
如果你不能对List和Map做任何假设。那么最好的方法是迭代前者,以最简单的方式突出一次;也就是说,使用 map
函数。
list.map { a =>
map
.get(key = a.x)
.fold(ifEmpty = a) { s =>
a.copy(s = s)
}
}
但是,当且仅当,您可以确定大部分时间:
- 列表会很大。
- 地图会很小。
- Map 中的键是 List 中值的子集。
- 并且所有操作都将更靠近列表的头部而不是尾部。
然后,您可以使用以下方法,这在这种情况下应该更有效。
def optimizedUpdate(data: List[A],updates: Map[Int,String]): List[A] = {
@annotation.tailrec
def loop(remaining: List[A],map: Map[Int,String],acc: List[A]): List[A] =
if (map.isEmpty) acc reverse_::: remaining
else remaining match {
case a :: as =>
map.get(key = a.x) match {
case None =>
loop(
remaining = as,map,a :: acc
)
case Some(s) =>
loop(
remaining = as,map = map - a.x,a.copy(s = s) :: acc
)
}
case Nil =>
acc.reverse
}
loop(remaining = data,map = updates,acc = List.empty)
}
但是请注意,代码不仅更长,而且更难理解。
它实际上比map
解决方案效率更低(如果条件不满足);这是因为 stdlib 实现 “欺骗” 并构造了 List 我改变了它的 tail
而不是向后构建它然后 reversing
它像我们一样有。
无论如何,与任何事物的性能一样,唯一真正的答案是基准测试。
但是,如果您确实需要速度,我会使用 map
解决方案只是为了清晰起见或使用可变方法。
您可以看到运行 here 的代码。
,怎么样
def update(as: List[A],String]): List[A] =
as.foldLeft(List.empty[A]) { (agg,elem) =>
val newA = map
.get(elem.x)
.map(a => elem.copy(s = a))
.getOrElse(elem)
newA :: agg
}.reverse