【问题标题】:Improvements for a Map merge function地图合并功能的改进
【发布时间】:2011-09-21 22:09:44
【问题描述】:

我正在编写一个函数来将两个 Map 合并在一起。这是我目前所拥有的:

def merge[K, V1, V2, V3](left: Map[K, V1], right: Map[K, V2])
    (fn: (Option[V1], Option[V2]) => V3): Map[K, V3] = {
  val r = (left.keySet ++ right.keySet) map {
    key =>
      (key -> fn(left.get(key), right.get(key)))
  }
  r.toMap
}

函数本身有效。你这样使用函数:

val m1 = Map(1 -> "one", 3 -> "three", 5 -> "five")
val m2 = Map(1 -> "I", 5 -> "V", 10 -> "X")
merge(m1, m2) { (_, _) } 
// returns: 
// Map(1 -> (Some(one),Some(I)), 
//     3 -> (Some(three),None), 
//     5 -> (Some(five),Some(V)), 
//     10 -> (None,Some(X)))

我有两个问题:

  1. 我担心.get.toMap 调用的性能 计算复杂度。任何人都可以改进实施吗?
  2. 我希望默认函数生成一对值 ({ (_, _) })。我无法完全掌握正确执行此操作的语法。

编辑: 虽然我最初说的是性能,但我指的是计算复杂度。 我的猜测是这个函数在 O(n•ln(n)) 时间内执行。 看起来我的函数大致在 O(n) 时间内执行。可以在 O(ln(n)) 内完成吗?

【问题讨论】:

  • 回答问题 1:您可能会看看 Scalaz 的映射类幺半群,如 Eric Torreborre 的 illustrated。你的问题有点不同,但它可能会给你一些想法。
  • 请看上面的例子。
  • @Kipton 地图幺半群令人印象深刻。我得考虑一下,看看这是否适用。

标签: scala


【解决方案1】:

对于默认函数字面量使用:

(fn: (Option[V1], Option[V2]) => V3 = 
  (x: Option[V1], y: Option[V2]) => Tuple2(x,y))

你必须像这样使用合并:merge(m1,m2)()

我想说,在您对实际数据进行一些测量之前,不要担心性能。

编辑:关于性能,通过提供视图而不是构造地图,您可以快速“构造”而牺牲查找 - 假设我们正在处理不可变地图。因此,根据实际数据和用例,您可以获得更好的某些操作的性能,但它有一个权衡。

class MergedView[K, V1, V2, V3](
    left: Map[K, V1], right: Map[K, V2]
  )(fn: (Option[V1], Option[V2]) => V3 = (x: Option[V1], y: Option[V2]) => Tuple2(x,y)
  ) extends collection.DefaultMap[K, V3] {
  def get(key: K): Option[V3] = (left.get(key), right.get(key)) match {
    case (None, None) => None
    case t => Some(fn(t._1, t._2))
  }
  lazy val tuples = (left.keys ++ right.keys).map(key => key -> get(key).get)
  def iterator: Iterator[(K, V3)] = tuples.iterator
} 

val r1 = new MergedView(m1, m2)() // use parens here for second param list.

【讨论】:

  • 从未想过创建视图对象。很有意思!这对我的用例没有帮助,但我以后会记住这一点。
  • huynhjl 这太棒了。 @dave 我对您的回复感到困惑,因为这不是通用解决方案。我可以使用... r1.toMap 将视图转回地图
  • @huynhjl,我如何在其中引入我自己的密钥排序?谢谢。
  • @Core - 我忘记了为什么它在我的特定情况下不起作用。
【解决方案2】:

你不应该担心get——是的,它会创建一个包装器对象,但做其他任何事情都会很尴尬,除非分析器显示这是一个问题,否则你不应该尝试。

至于toMap,是的,这可能会让你慢下来。您可以尝试使用breakOut

关于gettoMap的复杂度,对于不可变的HashMap,查找和添加是有效的常数时间,这是默认的Map。见Performance Characteristics of Scala Collections

【讨论】:

  • 比起调用 O(ln(n)) 函数 n 次的性能,我不太担心创建包装对象(无论如何我都需要)的性能。
  • @dave 查找(即get)对于默认映射(HashMap)是有效的常数时间(取决于散列的质量)。请参阅 Scala Collections 的 performance characteristics
  • 优秀的链接!我一直在寻找类似的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
  • 1970-01-01
  • 2010-11-04
  • 1970-01-01
  • 1970-01-01
  • 2016-01-10
  • 2019-09-13
相关资源
最近更新 更多