【问题标题】:How to replace or append an item in/to a list?如何在列表中替换或附加项目?
【发布时间】:2019-08-23 11:16:30
【问题描述】:

假设我有一个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只传递一次输入列表?

【问题讨论】:

  • 考虑到id 作为键,为什么不使用Map 而不是List ...
  • 假设给定了replaceOrAppend 签名。当然,您可以将列表转换为地图,将元素添加到地图中,然后再次将地图转换为列表。我只是不确定这会是更好的方法。
  • 为什么需要列表,顺序重要吗?
  • 这看起来是一个 X/Y 问题(假设 List 是正确的方法......)
  • @LuisMiguelMejíaSuárez 是的,输入列表顺序很重要。

标签: scala collections


【解决方案1】:

如果订单不是必需的,我会选择:

def replaceOrAppend(as: List[A], a: A): List[A] =
  a::as.filterNot(_.id == a.id) 

如果订单与idstr 相关,这也可以:

def replaceOrAppend(as: List[A], a: A): List[A] =
  (a::as.filterNot(_.id == a.id)).sortBy(_.id)

如果必须保留订单(正如 Micheal 建议的那样 - 我找不到更好的东西):

 def replaceOrAppend(as: List[A], a: A): List[A] =
  as.span(_.id != a.id) match { case (xs, ys) => xs ++ (a :: ys.drop(1)) }

【讨论】:

  • 谢谢,但是如果输入列表的顺序很重要呢?
  • 所以如果附加 A 总是在末尾?
  • 是的,a 应该在 as 的末尾(如果附加)。
  • 你觉得def replaceOrAppend(as: List[A], a: A): List[A] = as.span(_.id != a.id) match { case (xs, y :: ys) => xs ++ (a +: ys); case (xs, Nil) => xs :+ a }怎么样?
  • 如果必须保持订单,我认为这是你能做的最好的事情——我将它添加到我的答案中;)。
【解决方案2】:

这是另一个:

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

【讨论】:

  • 看起来你的函数传递了输入列表两次,但我要求一个函数传递输入一次。不错的尝试:)
【解决方案3】:

这个呢?仍然很笨拙,但只使用了一次迭代。

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

【讨论】:

  • 谢谢。确实看起来很笨拙,但也许我们可以改进它。
【解决方案4】:

我不明白为什么人们忘记了处理函数列表的最佳方法是通过模式匹配 + 尾递归
恕我直言,这看起来更干净,并试图尽可能高效。

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

从技术上讲,这在最坏的情况下会遍历列表两次。
但是,建立一个列表总是比做很多追加要好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-24
    • 2021-12-22
    • 2018-01-25
    • 2019-12-02
    • 2011-12-15
    • 1970-01-01
    • 2012-11-08
    相关资源
    最近更新 更多