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

发布于 2024-09-08 08:24:51 字数 2675 浏览 3 评论 0原文

我发现自己使用了很多嵌套映射,例如 Map[Int、Map[String、Set[String]]],并且我希望在访问新密钥时自动创建新的 Map、Set 等。例如,如下所示:

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

任何人都可以看到一种方法这样做?

I find myself using a lot of nested maps, e.g a Map[Int, Map[String, Set[String]]], and I'd like to have new Maps, Sets, etc. created automatically when I access a new key. E.g. something like the following:

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

Note that I don't want to use getOrElseUpdate here if I don't have to because it gets pretty verbose when you have nested maps and obscures what's actually going on in the code:

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

So I'm overriding HashMap's "default" method. I've tried two ways of doing this, but neither is fully satisfactory. My first solution was to write a method that created the map, but it seems that I still have to specify the full nested Map type when I declare the variable or things don't work:

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

My second solution was to write a factory class with a method, and that way I only have to declare each type a single time. But then each time I want a new default valued map, I have to both instantiate the factory class and then call the method, which still seems a little verbose:

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

I'd really like to have something as simple as this:

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

Anyone see a way to do that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

莫相离 2024-09-15 08:24:51

对于 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 不是很清楚。我特别不喜欢有副作用的地图访问。

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

You can't curry type parameters in Scala, therefore the trick with the class to capture the key type is necessary.

By the way: I don't think that the resulting API is very clear. I particularly dislike the side-effecting map access.

挽心 2024-09-15 08:24:51

事实证明,我还需要扩展 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))

Turns out I need to extend MapLike as well, or when I call filter, map, etc. my default valued map will get turned back into a regular Map without the defaulting semantics. Here's a variant of mkneissl's solution that does the right thing for filter, map, etc.

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

And here that is, in action, doing the right thing with filter:

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))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文