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

时间:2019-01-03 02:34:25

标签: 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)
  } 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
    }
  }
}

2 个答案:

答案 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进行另一遍处理。

我相信很明显如何结合使用mapvar

注意List.map的性能与列表上的一次传递相同,仅是因为标准库在内部是可变的。特别是cons类::被声明为

final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {

所以tl实际上是一个varmap实现利用它来以有效的方式从头开始建立列表。该字段为private[scala],因此您不能在标准库之外使用相同的技巧。不幸的是,我没有看到任何其他API调用可以使用此功能将问题的复杂性降低到一遍。