Scala: возможна ли HashMap с разными типами данных для разных ключей?
Я совершенно новичок в программировании на Scala, и у меня возникла следующая проблема:
Мне нужен HashMap, который может содержать много типов данных (Int, String и т. Д.). В C++ я бы использовал MultiMap от BOOST. Я слышал, что в Scala есть черта MultiMap. В основном я хочу следующее:
val map = HashMap[String, ListBuffer[_]]
Конкретный тип данных элементов ListBuffer должен быть определен во время выполнения. Когда я тестирую эту реализацию в консоли, работает следующее:
scala> val a = new HashMap[String, ListBuffer[_]]()
a: scala.collection.mutable.HashMap[String,scala.collection.mutable.ListBuffer[_]] = Map()
scala> val b = new ListBuffer[String]()
b: scala.collection.mutable.ListBuffer[String] = ListBuffer()
scala> val c = new ListBuffer[Int]()
c: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
scala> b += "String"
res0: b.type = ListBuffer(String)
scala> c += 1
res1: c.type = ListBuffer(1)
scala> a += "String Buffer" -> b
res2: a.type = Map((String Buffer,ListBuffer(String)))
scala> a += "This is an Int Buffer" -> c
res3: a.type = Map((String Buffer,ListBuffer(String)), (This is an Int Buffer,ListBuffer(1)))
Так что в основном это работает. Мой первый вопрос: есть ли возможность реализовать такое же поведение в Scala без использования ListBuffer в качестве уровня косвенности.
Например, получить карту со следующим содержимым: Карта ((String, 1),(String, "String value"), ...)
Когда я сейчас пытаюсь использовать реализацию ListBuffer-выше, я получаю следующую ошибку несоответствия типов:
found : _$1 where type _$1
required: _$3 where type _$3
Я в основном пытаюсь сделать следующее:
Я использую итератор для перебора ключей карты:
var valueIds = new ListBuffer[Int]()
val iterator = map.keys
iterator.foreach(key => { valueIds += setValue((map.apply(key)).last) }
setValue возвращает Int и является методом, который должен что-то делать с последним элементом ListBuffers.
Кто-нибудь знает, как исправить вышеупомянутое несоответствие типов?
Спасибо за вашу помощь!
С уважением
4 ответа
Вам нужно хранить несколько значений для каждого ключа? Если это так, используя ListBuffer
или другая коллекция необходима в любом случае. (В Scala MultiMap
храните наборы, поэтому, если вам нужно хранить дубликаты, они не будут работать.)
Если вам не нужно хранить несколько значений для каждого ключа, тогда вам просто нужно Any
тип:
scala> val map = collection.mutable.HashMap[String,Any]()
map: scala.collection.mutable.HashMap[String,Any] = Map()
scala> map += "One" -> 1
res1: map.type = Map((One,1))
scala> map += "Two" -> "ii"
res2: map.type = Map((Two,ii), (One,1))
scala> map += "Three" -> None
res3: map.type = Map((Three,None), (Two,ii), (One,1))
где вам теперь, вероятно, нужно будет выполнить сопоставление с шаблоном или использовать сбор, чтобы сделать что-нибудь полезное для значений:
scala> map.values.foreach(_ match { case i: Int => println("We stored the number "+i) })
We stored the number 1
scala> map.values.collect{ case i: Int => i }
res4: Iterable[Int] = List(1)
Scala имеет класс MultiMap
scala> import scala.collection.mutable.{HashMap, MultiMap, Set}
import scala.collection.mutable.{HashMap, MultiMap, Set}
scala> val a = new HashMap[String, Set[Any]] with MultiMap[String, Any]
a: scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[Any]] with scala.collection.mutable.MultiMap[String,Any] = Map()
scala> a.addBinding("Pants", 1)
res0: a.type = Map((Pants,Set(1)))
scala> a.addBinding("Pants", 2)
res1: a.type = Map((Pants,Set(1, 2)))
scala> a.addBinding("Trousers", 3)
res2: a.type = Map((Trousers,Set(3)), (Pants,Set(1, 2)))
scala> a.mapValues { v => v.last }
res3: scala.collection.Map[String,Any] = Map((Trousers,3), (Pants,2))
scala> val valueIds = a.values.flatten
valueIds: Iterable[Any] = List(3, 1, 2)
Я думаю, что это имеет смысл, когда вы смотрите на ваш код относительно того, что вы хотите.
Если вы используете заполнитель _
в качестве параметра типа это переводится в экзистенциальный тип, т. е. ваше определение ListBuffer[_]
становится ListBuffer[A] forSome { type A }
, Это означает, что компилятор ничего не знает об этом типе A
и не может делать какие-либо предположения об этом.
Самым простым решением было бы просто использовать ListBuffer[Any]
и оберните карту примерно так:
val m = new HashMap[String,ListBuffer[Any]]
def get(key: String) =
m.getOrElseUpdate(key, new ListBuffer())
get("Strings") += "a"
get("Strings") += "b" += "c"
get("Ints") += 1 += 2
// m is now:
// Map(Ints -> ListBuffer(1, 2), Strings -> ListBuffer(a, b, c))
Позвольте мне предложить еще один способ:
import scala.collection.mutable
val map = mutable.Map.empty[String, Vector[Any]].withDefaultValue(Vector.empty)
map("strings") :+= "one"
map("ints") :+= 1
map("ints") ++= Seq(1, 2)
assert {
map("default") == Vector.empty && // no side effects here
!map.contains("default")
}