【问题标题】:Designing a convenient default valued map in Scala在 Scala 中设计一个方便的默认值映射
【发布时间】:2010-07-06 15:01:48
【问题描述】:

我发现自己使用了很多嵌套地图,例如 Map[Int, Map[String, Set[String]]],我希望在访问时自动创建新的 Maps、Sets 等新钥匙。例如。类似于以下内容:

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

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

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

所以我要覆盖 HashMap 的“默认”方法。我尝试了两种方法来做到这一点,但都不是完全令人满意的。我的第一个解决方案是编写一个创建地图的方法,但似乎我在声明变量时仍然必须指定完整的嵌套地图类型,否则事情不起作用:

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]()))

有人知道这样做的方法吗?

【问题讨论】:

    标签: scala collections type-inference


    【解决方案1】:

    使用 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 中对类型参数进行 curry,因此需要使用类来捕获键类型。

    顺便说一句:我认为生成的 API 不是很清楚。我特别不喜欢有副作用的地图访问。

    【讨论】:

    • 我明白了,诀窍是在工厂类的顶部使用附带的对象层。谢谢,很酷!出于好奇,您会使用什么 API?我试图避免上面第二个代码块中显示的嵌套 getOrElseUpdate 暴行。我当然愿意接受其他方式。
    【解决方案2】:

    原来我也需要扩展 MapLike,或者当我调用过滤器、地图等时,我的默认值地图将变成没有默认语义的常规地图。这是 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))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 2018-12-21
      • 2012-11-28
      • 1970-01-01
      • 2014-11-27
      • 1970-01-01
      相关资源
      最近更新 更多