我正在尝试找到一种优雅的方法:
val l = List(1,2,3)
val (item, idx) = l.zipWithIndex.find(predicate)
val updatedItem = updating(item)
l.update(idx, updatedItem)
我可以一次完成所有操作吗?找到该项目(如果存在),并用更新的值替换并保持在原位。
我可以做到:
l.map{ i =>
if (predicate(i)) {
updating(i)
} else {
i
}
}
但这很丑陋。
另一个复杂之处是我只想更新与predicate
匹配的第一个元素。
编辑:尝试:
implicit class UpdateList[A](l: List[A]) {
def filterMap(p: A => Boolean)(update: A => A): List[A] = {
l.map(a => if (p(a)) update(a) else a)
}
def updateFirst(p: A => Boolean)(update: A => A): List[A] = {
val found = l.zipWithIndex.find { case (item, _) => p(item) }
found match {
case Some((item, idx)) => l.updated(idx, update(item))
case None => l
}
}
}
答案 0 :(得分:3)
您可以使用.zipWithIndex()
来避免.indexWhere()
。
要提高复杂度,请使用Vector
,以使l(idx)
成为有效的恒定时间。
val l = Vector(1,2,3)
val idx = l.indexWhere(predicate)
val updatedItem = updating(l(idx))
l.updated(idx, updatedItem)
使用scala.collection.immutable.Vector
而非List
的原因:
Scala的列表是一个链接列表,这意味着可以在O(n)时间内访问数据。 Scala的Vector已建立索引,这意味着可以在有效的恒定时间内从任何点读取数据。
如果您只修改非常大的集合中的一个元素,则也可以考虑使用可变集合。
https://docs.scala-lang.org/overviews/collections/performance-characteristics.html
答案 1 :(得分:3)
在不使用可变变量的情况下,我不知道有什么方法可以一次通过集合。通过两次通过,您可以使用foldLeft
进行操作,如下所示:
def updateFirst[A](list:List[A])(predicate:A => Boolean, newValue:A):List[A] = {
list.foldLeft((List.empty[A], predicate))((acc, it) => {acc match {
case (nl,pr) => if (pr(it)) (newValue::nl, _ => false) else (it::nl, pr)
}})._1.reverse
}
这个想法是foldLeft
允许通过迭代传递其他数据。在此特定实现中,我将谓词更改为始终返回false
的固定谓词。不幸的是,您无法高效地从头构建List
,因此这需要对reverse
进行另一遍处理。
我相信很明显如何结合使用map
和var
注意:List.map
的性能与列表上的一次传递相同,仅是因为标准库在内部是可变的。特别是cons类::
被声明为
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
所以tl
实际上是一个var
,map
实现利用它来以有效的方式从头开始建立列表。该字段为private[scala]
,因此您不能在标准库之外使用相同的技巧。不幸的是,我没有看到任何其他API调用可以使用此功能将问题的复杂性降低到一遍。