Я использую много вложенных карт, например, Карты [Интервал, Карта [Строка, Набор [Строка]]], и я хотел бы иметь новые Карты, Наборы, и т.д. созданные автоматически, когда я получаю доступ к новому ключу. Например, что-то как следующее:
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 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 очень понятен. Мне особенно не нравится доступ к карте с побочными эффектами.
Оказывается, мне также нужно расширить 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))