I currently have a variable that looks like this:
val someVal = new HashMap[Float, Set[String]] with MultiMap[Float, String]
Now I would like to have a hash of these hashes of the form:
val someHashOfSomeVal = new HashMap[String, HashMap[Float, Set[String]] with MultiMap[Float, String]]
In other words, I need to have a hash table (with multiple values for each key) of hash tables (with multiple values of each key). Can anyone help me with how I declare / mutate this variable?
Do I mutate it like this?someHashOfSomeVal.addBinding("someKey", someVal)
It's unclear to me why you'd want the top-level map to have multiple values (other maps, in this case) per key, or how that would work in practice. I'll assume that you only want MultiMap at the lower level, in which case you can use the following approach:
import scala.collection.mutable.{ HashMap, MultiMap, Set }
class TwoLevel[A, B, C] extends HashMap[A, MultiMap[B, C]] {
override def default(key: A) = new HashMap[B, Set[C]] with MultiMap[B, C]
def addTriple(a: A, b: B, c: C) {
this += a -> this(a).addBinding(b, c)
}
def lookupPair(a: A, b: B): Set[C] = this(a).getOrElse(b, Set.empty)
}
val m = new TwoLevel[String, Float, String]
m.addTriple("a", 1.0F, "b")
m.addTriple("a", 0.0F, "c")
println(m.lookupPair("a", 0.0F))
Which prints Set(c), as you'd expect.
Related
Suppose I have the following variables:
val m = HashMap( ("1", "one"), ("2", "two"), ("3", "three") )
val l = List("1", "2")
I would like to extract the list List("one","two"), which corresponds to the values for each key in the list present in the map.
This is my solution, works like a charm. Still I would like to know if I'm reinventing the wheel and if there's some idiomatic solution for doing what I intend to do:
class Mapper[T,V](val map: HashMap[T,V]) extends PartialFunction[T, V]{
override def isDefinedAt(x: T): Boolean = map.contains(x)
override def apply(x: T): V = map.get(x) match {
case Some(v) => v
}
}
val collected = l collect (new Mapper(map) )
List("one", "two")
Yes, you are reinventing the wheel. Your code is equivalent to
l collect m
but with additional layer of indirection that doesn't add anything to HashMap (which already implements PartialFunction—just expand the "Linear Supertypes" list to see that).
Alternatively, you can also use flatMap as follows:
l flatMap m.get
The implicit CanBuildFroms make sure that the result is actually a List.
You could do this, which seems a bit simpler:
val res = l.map(m.get(_)) // List(Some("one"), Some("two"))
.flatMap(_.toList)
Or even this, using a for-comprehension:
val res = for {
key <- l
value <- m.get(key)
} yield value
I would suggest something like this:
m.collect { case (k, v) if l.contains(k) => v }
note:
does not preserve the order from l
does not handle the case of duplicates in l
I'm using ListMap because I need to keep the insertion order in place. After initializing it seems it works. but when I call updated on it the order gets messed up. 1- Why is that? 2- Is there any other MapLike that doesn't have this problem, if not how should I update the map without problem?
scala> import scala.collection.immutable.ListMap
import scala.collection.immutable.ListMap
scala> val a = ListMap(0 -> "A", 1 -> "B", 2 ->"C")
a: scala.collection.immutable.ListMap[Int,String] = Map(0 -> A, 1 -> B, 2 -> C)
scala> a.foreach(println)
(0,A)
(1,B)
(2,C)
scala> val b = a.updated(1, "D")
b: scala.collection.immutable.ListMap[Int,String] = Map(0 -> A, 2 -> C, 1 -> D)
scala> b.foreach(println)
(0,A)
(2,C)
(1,D)
I could not find any existent immutable collection with desired property. But it could be crafted manually.
import scala.collection.immutable.{IntMap, Map, MapLike}
class OrderedMap[K, +V] private[OrderedMap](backing: Map[K, V], val order: IntMap[K], coorder: Map[K, Int], extSize: Int)
extends Map[K, V] with MapLike[K, V, OrderedMap[K, V]] {
def +[B1 >: V](kv: (K, B1)): OrderedMap[K, B1] = {
val (k, v) = kv
if (backing contains k)
new OrderedMap(backing + kv, order, coorder, extSize)
else new OrderedMap(backing + kv, order + (extSize -> k), coorder + (k -> extSize), extSize + 1)
}
def get(key: K): Option[V] = backing.get(key)
def iterator: Iterator[(K, V)] = for (key <- order.valuesIterator) yield (key, backing(key))
def -(key: K): OrderedMap[K, V] = if (backing contains key) {
val index = coorder(key)
new OrderedMap(backing - key, order - index, coorder - key, extSize)
} else this
override def empty: OrderedMap[K, V] = OrderedMap.empty[K, V]
}
object OrderedMap {
def empty[K, V] = new OrderedMap[K, V](Map.empty, IntMap.empty, Map.empty, 0)
def apply[K, V](assocs: (K, V)*): OrderedMap[K, V] = assocs.foldLeft(empty[K, V])(_ + _)
}
Here order is preserved insertion order map (probably with "holes"). coorder special field needed for efficient handling element removal. extSize is basically order.lastkey + 1 but more straightforward
Now you can verify that
val a = OrderedMap(0 -> "A", 1 -> "B", 2 -> "C")
a.foreach(println)
val b = a.updated(1, "D")
b.foreach(println)
prints
(0,A)
(1,B)
(2,C)
and
(0,A)
(1,D)
(2,C)
From the scala doc for updated
"This method allows one to create a new map with an additional mapping
from key to value."
Note it does not say "with a different value of an existing key". So when you updated with 1->D, that's a new/additional mapping. So it appears at the end of the list, preserving insertion order. The old mapping 1->C is no longer present in the map.
So it's not "messed up" and it's not a problem. It's doing what it's documented to do, the mappings are in insertion order.
I have a list which I am combining to a map in this way, by calling the respective value calculation function. I am using collection.breakout to avoid creating unnecessary intermediate collections since what I am doing is a bit combinatorial, and every little bit of saved iterations helps.
I need to filter out certain tuples from the map, in my case where the value is less than 0. Is it possible to add this to the map itself rather than doing a filter afterwards (thus iterating once again)?
val myMap: Map[Key, Int] = keyList.map(key => key -> computeValue(key))(collection.breakOut)
val myFilteredMap = myMap.filter(_._2 >= 0)
In other words I wish to obtain the second map ideally at one go, so ideally in the first call to map() I filter out the tuples I don't want. Is this possible in any way?
You can easily do this with a foldLeft:
keyList.foldLeft( Map[Key,Int]() ) {
(map, key) =>
val value = computeValue(key)
if ( value >= 0 ) {
map + (key -> value)
} else {
map
}
}
It would probably be best to do a flatMap:
import collection.breakOut
type Key = Int
val keyList = List(-1,0,1,2,3)
def computeValue(i: Int) = i*2
val myMap: Map[Key, Int] =
keyList.flatMap { key =>
val v = computeValue(key)
if (v >= 0) Some(key -> v)
else None
}(breakOut)
You can use collect
val myMap: Map[Key, Int] =
keyList.collect {
case key if computeValue(key) >= 0 => key -> computeValue(key)
}(breakOut)
But that requires re-computing computeValue(key), which is silly. Collect is better when you filter then map.
Or make your own method!:
import scala.collection.generic.CanBuildFrom
import scala.collection.TraversableLike
implicit class EnrichedWithMapfilter[A, Repr](val self: TraversableLike[A, Repr]) extends AnyVal {
def maptofilter[B, That](f: A => B)(p: B => Boolean)(implicit bf: CanBuildFrom[Repr, (A, B), That]): That = {
val b = bf(self.asInstanceOf[Repr])
b.sizeHint(self)
for (x <- self) {
val v = f(x)
if (p(v))
b += x -> f(x)
}
b.result
}
}
val myMap: Map[Key, Int] = keyList.maptofilter(computeValue)(_ >= 0)(breakOut)
When I create an immutable map with a standard call to Map() or by concatenating the existing maps created that way, in all my tests I get that traversing its members provides them in the order of addition. That's exactly the way I need them to be sorted, but there's not a word in the documentation about the reliability of the ordering of the members of the map.
So I was wondering whether it is safe to expect the standard Map to return its items in the order of addition or I should look for some other implementations and which ones in that case.
I don't think it's safe, the order is not preserved starting from 5 elements (Scala 2.9.1):
scala> Map(1 -> 1, 2 -> 2, 3 -> 3, 4 -> 4, 5 -> 5)
res9: scala.collection.immutable.Map[Int,Int] =
Map(5 -> 5, 1 -> 1, 2 -> 2, 3 -> 3, 4 -> 4)
With bigger maps the order is completely "random", try Map((1 to 100) zip (1 to 100): _*).
Try LinkedHashMap for ordered entries and TreeMap to achieve sorted entries.
There is no promise about the order of Map. There is an OrderedMap in scalas collection package. The values in that package are ordered by an implicit Ordering. As quickfix I recommend you to use a list of keys for the ordering of your Map.
var keyOrdering = List[Int]()
var unorderedMap = Map[Int, String]()
unorderedMap += (1 -> "one")
keyOrdering :+= 1
Edit
You could implement your own Ordering and pass it to a SortedMap as well.
Edit #2
A simple example would be the following:
scala> import scala.collection.SortedMap
import scala.collection.SortedMap
scala> implicit object IntOrdering extends Ordering[Int]
| def compare(a: Int, b: Int) = b - a
| }
defined module IntOrdering
scala> var sm = SortedMap[Int, String]()
sm: scala.collection.SortedMap[Int,String] = Map()
scala> sm += (1 -> "one")
scala> sm += (2 -> "two")
scala> println(sm)
Map(2 -> two, 1 -> one)
The implicit Ordering is applied to the keys, so IntOrdering might be applied to a SortedMap[Int, Any].
Edit #3
A self ordering DataType like in my comment might look this way:
case class DataType[T](t: T, index: Int)
object DataType{
private var index = -1
def apply[T](t: T) = { index += 1 ; new DataType[T](t, index)
}
Now we need to change the Ordering:
implicit object DataTypeOrdering extends Ordering[DataType[_]] {
def compare(a: DataType[_], b: DataType[_]) = a.index - b.index
}
I hope this is the way you expected my answer.
After digging I've found out that there exists an immutable ListMap that behaves exactly as I want it, but according to this table its performance is just awfull. So I wrote a custom immutable implementation that should perform effectively on all operations except removal, where it performs linearly. It does require a bit more memory as it's backed by a standard Map and a Queue, which itself utilizes a List twice, but in the current age it's not an issue, right.
import collection.immutable.Queue
object OrderedMap {
def apply[A, B](elems: (A, B)*) =
new OrderedMap(Map(elems: _*), Queue(elems: _*))
}
class OrderedMap[A, B](
map: Map[A, B] = Map[A, B](),
protected val queue: Queue[(A, B)] = Queue()
) extends Map[A, B] {
def get(key: A) =
map.get(key)
def iterator =
queue.iterator
def +[B1 >: B](kv: (A, B1)) =
new OrderedMap(
map + kv,
queue enqueue kv
)
def -(key: A) =
new OrderedMap(
map - key,
queue filter (_._1 != key)
)
override def hashCode() =
queue.hashCode
override def equals(that: Any) =
that match {
case that: OrderedMap[A, B] =>
queue.equals(that.queue)
case _ =>
super.equals(that)
}
}
I'd like to link 2 columns of unique identifiers and be able to get a first column value by a second column value as well as a second column value by a first column value. Something like
Map(1 <-> "one", 2 <-> "two", 3 <-> "three")
Is there such a facility in Scala?
Actually I need even more: 3 columns to select any in a triplet by another in a triplet (individual values will never be met more than once in the entire map). But a 2-column bidirectional map can help too.
Guava has a bimap that you can use along with
import scala.collection.JavaConversions._
My BiMap approach:
object BiMap {
private[BiMap] trait MethodDistinctor
implicit object MethodDistinctor extends MethodDistinctor
}
case class BiMap[X, Y](map: Map[X, Y]) {
def this(tuples: (X,Y)*) = this(tuples.toMap)
private val reverseMap = map map (_.swap)
require(map.size == reverseMap.size, "no 1 to 1 relation")
def apply(x: X): Y = map(x)
def apply(y: Y)(implicit d: BiMap.MethodDistinctor): X = reverseMap(y)
val domain = map.keys
val codomain = reverseMap.keys
}
val biMap = new BiMap(1 -> "A", 2 -> "B")
println(biMap(1)) // A
println(biMap("B")) // 2
Of course one can add syntax for <-> instead of ->.
Here's a quick Scala wrapper for Guava's BiMap.
import com.google.common.{collect => guava}
import scala.collection.JavaConversions._
import scala.collection.mutable
import scala.languageFeature.implicitConversions
class MutableBiMap[A, B] private (
private val g: guava.BiMap[A, B] = new guava.HashBiMap[A, B]()) {
def inverse: MutableBiMap[B, A] = new MutableBiMap[B, A](g.inverse)
}
object MutableBiMap {
def empty[A, B]: MutableBiMap[A, B] = new MutableBiMap()
implicit def toMap[A, B] (x: MutableBiMap[A, B]): mutable.Map[A,B] = x.g
}
I have a really simple BiMap in Scala:
case class BiMap[A, B](elems: (A, B)*) {
def groupBy[X, Y](pairs: Seq[(X, Y)]) = pairs groupBy {_._1} mapValues {_ map {_._2} toSet}
val (left, right) = (groupBy(elems), groupBy(elems map {_.swap}))
def apply(key: A) = left(key)
def apply[C: ClassTag](key: B) = right(key)
}
Usage:
val biMap = BiMap(1 -> "x", 2 -> "y", 3 -> "x", 1 -> "y")
assert(biMap(1) == Set("x", "y"))
assert(biMap("x") == Set(1, 3))
I don't think it exists out of the box, because the generic behavior is not easy to extract
How to handle values matching several keys in a clean api?
However for specific cases here is a good exercise that might help. It must be updated because no hash is used and getting a key or value is O(n).
But the idea is to let you write something similar to what you propose, but using Seq instead of Map...
With the help of implicit and trait, plus find, you could emulate what you need with a kind of clean api (fromKey, fromValue).
The specificities is that a value is not supposed to appear in several places... In this implementation at least.
trait BiMapEntry[K, V] {
def key:K
def value:V
}
trait Sem[K] {
def k:K
def <->[V](v:V):BiMapEntry[K, V] = new BiMapEntry[K, V]() { val key = k; val value = v}
}
trait BiMap[K, V] {
def fromKey(k:K):Option[V]
def fromValue(v:V):Option[K]
}
object BiMap {
implicit def fromInt(i:Int):Sem[Int] = new Sem[Int] {
def k = i
}
implicit def fromSeq[K, V](s:Seq[BiMapEntry[K, V]]) = new BiMap[K, V] {
def fromKey(k:K):Option[V] = s.find(_.key == k).map(_.value)
def fromValue(v:V):Option[K] = s.find(_.value == v).map(_.key)
}
}
object test extends App {
import BiMap._
val a = 1 <-> "a"
val s = Seq(1 <-> "a", 2 <-> "b")
println(s.fromKey(2))
println(s.fromValue("a"))
}
Scala is immutable and values are assigned as reference not copy, so memory footprint will for reference/pointer storage only, which it's better to use to two maps, with type A being key for first and type being B being key for second mapped to B and A respectively, than tun time swapping of maps. And the swapping implementation also has it's own memory footprint and the newly swapped hash-map will also be there in memory till the execution of parent call back and the garbage collector call. And if the the swapping of map is required frequently than virtually your are using equally or more memory than the naive two maps implementation at starting.
One more approach you can try with single map is this(will work only for getting key using mapped value):
def getKeyByValue[A,B](map: Map[A,B], value: B):Option[A] = hashMap.find((a:A,b:B) => b == value)
Code for Scala implementation of find by key:
/** Find entry with given key in table, null if not found.
*/
#deprecatedOverriding("No sensible way to override findEntry as private findEntry0 is used in multiple places internally.", "2.11.0")
protected def findEntry(key: A): Entry =
findEntry0(key, index(elemHashCode(key)))
private[this] def findEntry0(key: A, h: Int): Entry = {
var e = table(h).asInstanceOf[Entry]
while (e != null && !elemEquals(e.key, key)) e = e.next
e
}