使用默认值填充可变映射或替换Scala函数memoization

时间:2016-02-04 22:48:53

标签: scala dictionary default-value

我知道withDefault允许您指定一个函数,该函数将应用于键以生成默认的映射值,但每次将未知键传递到地图时都会调用该函数,即使您传递相同的密钥 - 值不会存储在地图中。

scala> case class Foo(foo: String) {}
defined class Foo

scala> val m = mutable.OpenHashMap[String, Foo]().withDefault( s => {
       | println(s"creating new value for $s")
       | Foo(s)
       | })
m: scala.collection.mutable.Map[String,Foo] = Map()

scala> m("elvis")
creating value for elvis
res1: Foo = Foo(elvis)

scala> m("elvis")
creating value for elvis
res2: Foo = Foo(elvis)

现在显然这对案例类来说并不是什么大不了的事,但如果这些值本身就是可变集合,或者任何其他可变对象,那么它并不是很有用。无论我在何处访问地图,我都会使用getOrElseUpdate。当然,这是一个更优雅的解决方案吗?

或者,因为在这种情况下,&#39;默认&#39;函数实际上是唯一的方式我将生成地图值,是否有一个优雅的,类似Scala的解决方案,用于缓存函数返回值并不涉及可变映射?< / p>

ETA:为了清楚起见,我确实想为每个不同的键再次调用该函数。但是对于给定的键,我想只调用一次该函数,并在下次返回相同的对象。

2 个答案:

答案 0 :(得分:4)

Scalas mutable.Map实际上是设计为子类,因此可以覆盖default。默认default只会抛出NoSuchElementException,而现有的withDefault(Value)方法只会覆盖default。您可以在缓存值时执行相同的操作。

scala> import scala.collection.mutable
import scala.collection.mutable

scala> case class Foo(foo: String)
defined class Foo

scala> val m = new mutable.OpenHashMap[String, Foo] {
     |   override def default(key: String): Foo = {
     |     println(s"creating new value for $key")
     |     val foo = Foo(key)
     |     put(key, foo)
     |     foo
     |   }
     | }
m: scala.collection.mutable.OpenHashMap[String,Foo] = OpenHashMap()

scala> m("elvis")
creating new value for elvis
res0: Foo = Foo(elvis)

scala> m("elvis")
res1: Foo = Foo(elvis)

scala> m("not elvis")
creating new value for not elvis
res2: Foo = Foo(not elvis)

答案 1 :(得分:0)

试试这个解决方案。它需要改进。但主要思想包括:

  case class Foo(foo: String) {}

  implicit class MapExt[K, V](m: mutable.Map[K, V]) {
    def withCachedDefault(f: K => V): mutable.Map[K, V] = {
      new mutable.Map[K, V] { // create wrapper around 'm'
        override def apply(k: K): V = this.get(k) match {
          case None =>
            val v: V = f(k) 
            m.put(k, v)       // caching new non-existing value
            v
          case Some(v) => v
        }
        override def get(key: K): Option[V] = m.get(key)
        override def iterator: Iterator[(K, V)] = m.iterator
        override def +=(kv: (K, V)): this.type = ??? /* TODO wrap m.+= */
        override def -=(key: K): this.type = ??? /* TODO wrap m.-= */
      }
    }
  }

  val m = mutable.OpenHashMap[String, Foo]().withCachedDefault(s => {
    println(s"creating new value for $s")
    Foo(s)
  })

 println(m("elvis"))
 println(m("elvis"))
 println(m("word"))
 println(m("word"))

 // output:
 //> creating new value for elvis
 //> Foo(elvis)
 //> Foo(elvis)
 //> creating new value for word
 //> Foo(word)
 //> Foo(word)

该解决方案存在一些需要改进的问题。例如,可变状态的不可预测性:

val m = mutable.OpenHashMap[String, Foo]().withCachedDefault(fun1)

val m2 = m.withCachedDefault(fun2)
m2("elvis")     
m2("new value") 

等等。