Scala:查找并更新列表中的一个元素

Scala:查找并更新列表中的一个元素,scala,functional-programming,scalaz,Scala,Functional Programming,Scalaz,我试图找到一种优雅的方式: 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)

我试图找到一种优雅的方式:

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
  }
}
但那太难看了

另一个复杂性是,我只想更新与
谓词
匹配的第一个元素

编辑:尝试:

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
    }
  }
}

使用
.indexWhere()
可以避免
.zipWithIndex()

要提高复杂性,请使用
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的向量被编入索引,这意味着数据可以在有效恒定的时间内从任意点读取

val l = Vector(1,2,3)
val idx = l.indexWhere(predicate)
val updatedItem = updating(l(idx))
l.updated(idx, updatedItem)

如果在一个非常大的集合中只修改一个元素,也可以考虑可变集合。


我不知道如何在不使用可变变量的情况下在一次收集过程中实现这一点。通过两次传递,您可以使用
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
的固定谓词。不幸的是,您无法以有效的方式从头部构建
列表
,因此这需要另一个
反向
过程

我相信很明显,如何结合使用
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调用允许使用此功能将问题的复杂性降低到一次。

我刚刚意识到这个问题,哈哈。我只想在找到的第一个项目上执行此操作。考虑到我只能考虑使用可变变量跟踪是否已经进行了更新,并使用
if(!alreadyUpdated&&predicate(I))
-将所有内容封装在一个方法中,以避免暴露可变变量。对于一个完全不可变的解决方案,我将使用一个
foldLeft
,其中您将累积新列表(向后)和
alreadyUpdated
的标志,在折叠之后,您可以提取列表,然后将其反向-但是它将具有复杂度2O(N)(两次迭代),并且可能太复杂。不确定是否有更好的方法,这就是我不回答的原因。然后我觉得我的
updateFirst
方法是迄今为止最好的方法?Wonay,我不知道。意图非常清楚,这是一个要点但它会进行大量迭代(如果第一个元素接近末尾,则为O(3N))并创建一个额外的中间集合。如果你确信收集的物品会很小,而且第一件物品会在接近开始的时候,那就不会那么糟糕了。Aki,我不认为这是一个好主意,因为他/她也需要元素,因此需要再次使用该索引调用apply,这将导致另一次迭代,但会使意图更加明确!,这是一个要点!另一个想法是使用Vector而不是List。如何使用Vector?这并不比作者的代码好,因为
l(idx)
O(N)
你介意用Vector编辑你的解决方案吗?任何代码会改变还是仅仅是性能改变?Wonay正如Leighton已经说过的那样,代码不会改变,但是它的性能会更好。但是请注意
向量