在Scala中设计一个方便的默认值映射

时间:2010-07-06 15:01:49

标签: scala collections type-inference

我发现自己使用了很多嵌套地图,例如Map [Int,Map [String,Set [String]]],我想在访问时自动创建新的地图,集合等。新钥匙。例如。如下所示:

val m = ...
m(1992)("foo") += "bar"

请注意,如果我不需要,我不想在这里使用getOrElseUpdate,因为当你有嵌套的地图并且模糊了代码中实际发生的事情时它会变得非常冗长:

m.getOrElseUpdate(1992, Map[String, Set[String]]()).getOrElseUpdate("foo", Set[String]()) ++= "bar"

所以我重写了HashMap的“默认”方法。我尝试了两种方法,但都没有完全令人满意。我的第一个解决方案是编写一个创建地图的方法,但是当我声明变量或者事情不起作用时,我仍然需要指定完整的嵌套Map类型:

scala> def defaultingMap[K, V](defaultValue: => V): Map[K, V] = new HashMap[K, V] {                      |   override def default(key: K) = {
 |     val result = defaultValue
 |     this(key) = result
 |     result
 |   }
 | }
defaultingMap: [K,V](defaultValue: => V)scala.collection.mutable.Map[K,V]

scala> val m: Map[Int, Map[String, Set[String]]] = defaultingMap(defaultingMap(Set[String]()))
m: scala.collection.mutable.Map[Int,scala.collection.mutable.Map[String,scala.collection.mutable.Set[String]]] = Map()

scala> m(1992)("foo") += "bar"; println(m)                                                    
Map(1992 -> Map(foo -> Set(bar)))

scala> val m = defaultingMap(defaultingMap(Set[String]()))
m: scala.collection.mutable.Map[Nothing,scala.collection.mutable.Map[Nothing,scala.collection.mutable.Set[String]]] = Map()

scala> m(1992)("foo") += "bar"; println(m)
<console>:11: error: type mismatch;
 found   : Int(1992)
 required: Nothing
       m(1992)("foo") += "bar"; println(m)
         ^

我的第二个解决方案是用一个方法编写一个工厂类,这样我只需要一次声明每个类型。但是每当我想要一个新的默认值映射时,我必须实例化工厂类然后调用方法,这看起来仍然有点冗长:

scala> class Factory[K] {                                       
 |   def create[V](defaultValue: => V) = new HashMap[K, V] {
 |     override def default(key: K) = {                     
 |       val result = defaultValue                          
 |       this(key) = result                                 
 |       result                                             
 |     }                                                    
 |   }                                                      
 | }                                                        
defined class Factory

scala> val m = new Factory[Int].create(new Factory[String].create(Set[String]()))
m: scala.collection.mutable.HashMap[Int,scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[String]]] = Map()

scala> m(1992)("foo") += "bar"; println(m)
Map(1992 -> Map(foo -> Set(bar)))

我真的很喜欢这样简单:

val m = defaultingMap[Int](defaultingMap[String](Set[String]()))

有人看到了这样做的方法吗?

2 个答案:

答案 0 :(得分:5)

使用Scala 2.8:

object DefaultingMap {
  import collection.mutable
  class defaultingMap[K] {
    def apply[V](v: V): mutable.Map[K,V] = new mutable.HashMap[K,V] {
      override def default(k: K): V = {
        this(k) = v
        v
      }
    }
  }
  object defaultingMap {
    def apply[K] = new defaultingMap[K]
  }

  def main(args: Array[String]) {
    val d4 = defaultingMap[Int](4)
    assert(d4(3) == 4)
    val m = defaultingMap[Int](defaultingMap[String](Set[String]()))
    m(1992)("foo") += "bar"
    println(m)
  }
}

您无法在Scala中调整类型参数,因此必须使用类来捕获密钥类型。

顺便说一下:我认为最终的API不是很清楚。我特别不喜欢副作用地图访问。

答案 1 :(得分:3)

原来我还需要扩展MapLike,或者当我调用filter,map等时,我的默认值map将被转换回常规Map而不会出现默认语义。这是mkneissl解决方案的变体,它可以为过滤器,地图等做正确的事。

import scala.collection.mutable.{MapLike,Map,HashMap}

class DefaultingMap[K, V](defaultValue: => V) extends HashMap[K, V]
with MapLike[K, V, DefaultingMap[K, V]] {
  override def empty = new DefaultingMap[K, V](defaultValue)
  override def default(key: K): V = {
    val result = this.defaultValue
    this(key) = result
    result
  }
}

object DefaultingMap {
  def apply[K] = new Factory[K]
  class Factory[K] {
    def apply[V](defaultValue: => V) = new DefaultingMap[K, V](defaultValue)
  }
}

就是这样,在行动中,用过滤器做正确的事情:

scala> val m = DefaultingMap[String](0)
m: DefaultingMap[String,Int] = Map()

scala> for (s <- "the big black bug bit the big black bear".split(" ")) m(s) += 1

scala> val m2 = m.filter{case (_, count) => count > 1}
m2: DefaultingMap[String,Int] = Map((the,2), (big,2), (black,2))