【问题标题】:How to improve this "update" function?如何改进这个“更新”功能?
【发布时间】:2021-05-03 16:20:42
【问题描述】:

假设我有 case class A(x: Int, s: String) 并且需要像这样使用 Map[Int, String] 更新 List[A]:

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, "a"), A(2, "b1"), A(3, "c"), A(4, "d1"))

我这样写update:

def update(as: List[A], map: Map[Int, String]): List[A] = {

  @annotation.tailrec
  def loop(acc: List[A], rest: List[A], map: Map[Int, 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 功能?

【问题讨论】:

    标签: scala collections tail-recursion


    【解决方案1】:

    如果您无法对列表和地图做出任何假设。那么最好的办法就是迭代前者,以最简单的方式进行一次;也就是说,使用map 函数。

    list.map { a =>
      map
        .get(key = a.x)
        .fold(ifEmpty = a) { s =>
          a.copy(s = s)
       }
    }
    

    但是,当且仅当,您可以确定大多数时候:

    1. 列表会很大。
    2. 地图会很小。
    3. Map 中的键是List 中值的子集。
    4. 并且所有操作都将靠近 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 实现 "cheats" 并构造了 List 我改变了它的 tail,而不是像我们一样向后构建它然后 reversing 它。

    在任何情况下,与任何事物的性能一样,唯一真正的答案是进行基准测试。
    但是,如果您真的需要速度,我会使用 map 解决方案,只是为了清楚起见或使用可变方法。


    可以看到运行here的代码。

    【讨论】:

      【解决方案2】:

      怎么样

      def update(as: List[A], map: Map[Int, 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-30
        • 2020-12-03
        • 1970-01-01
        • 2022-07-29
        • 1970-01-01
        • 2013-08-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多