Разработка удобной карты по умолчанию в Scala - PullRequest
8 голосов
/ 06 июля 2010

Я использую много вложенных карт, например, 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.Я попробовал два способа сделать это, но ни один не вполне удовлетворителен.Моим первым решением было написать метод, который создал карту, но, похоже, мне все еще нужно указать полный вложенный тип карты, когда я объявляю переменную, или что-то не работает:

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 ]

5 голосов
/ 06 июля 2010

В 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, поэтому необходим трюк с классом для захвата типа ключа.

ByКстати: я не думаю, что полученный API очень понятен.Мне особенно не нравится побочный эффект доступа к карте.

3 голосов
/ 08 июля 2010

Оказывается, мне нужно также расширить MapLike, или когда я вызываю filter, 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))
...