Mutable map with multiple values in Scala - scala

I want to have a map so that:
I can update it -> mutable
I can have multiple values -> old multi map
I.e. I need to have a collection of the form:
k1 -> v1, k1 -> v2, ..., k2 -> v1, k2 -> v2, k2 -> v3, etc.
At the end of the day, what I need is to collect all this information as:
k1 -> Seq(v1,v2), ..., k2 -> Seq(v1,v2,v3)
How can I do this in Scala?
Edit
I tried this in Scastie:
val m = new scala.collection.mutable.Map[Int,Int] with scala.collection.mutable.MultiMap[Int,Int]
m += (1 -> 2)
m += (1 -> 3)
m
But I get:
illegal inheritance;
<$anon: Int => Int with Int => scala.collection.mutable.Set[Int]> inherits different type instances of trait Map:
scala.collection.mutable.Map[Int,scala.collection.mutable.Set[Int]] and scala.collection.mutable.Map[Int,Int]
trait MultiMap in package mutable is deprecated (since 2.13.0): Use a scala.collection.mutable.MultiDict in the scala-collection-contrib module

When people say "avoid using mutable structures" in scala, it especially applies to those, who have to ask questions about how to create a multimap.
I mean, it's really no rocket science:
val foo = mutable.Map[Int, List[Int]]().withDefault(_ => Nil)
foo += 1 -> (1::foo(1))
foo += 1 -> (2::foo(1))
foo += 1 -> foo(1).filterNot(Set)
foo += 1 -> foo(1).tail
tuples.foreach { case (k,v) => foo += k -> (v::foo(k)) }
//etc.
But if you have to ask how to do this, chances are you are going to end up using it wrong (which is very easy with mutable structure). I strongly encourage you to stick with immutable collections (and avoid mutable state in general) until you got enough command of the language to be able to definitively identify situations where mutable is actually the correct solution.

As it was discussed in the comments section, you can proceed with MultiDict from scala-collection-contrlib library. Example:
import scala.collection.mutable
def convert[K, V](iterable: Iterable[(K, V)]): mutable.MultiDict[K, V] = {
iterable.foldLeft(mutable.MultiDict.empty[K, V])(_ addOne _)
}
val list = List("k1" -> "v1", "k1" -> "v2", "k1" -> "v3", "k2" -> "v1", "k3" -> "v1")
val map = convert(list)
println(map.sets)
Will print: HashMap(k1 -> HashSet(v1, v2, v3), k2 -> HashSet(v1), k3 -> HashSet(v1))
Scatie: https://scastie.scala-lang.org/uULFEKUwTVaSHLpJosnXYw

Related

Merge two LinkedHashMap in Scala

Having this code
def mergeWith[K, X, Y, Z](xs: mutable.LinkedHashMap[K, X], ys: mutable.LinkedHashMap[K, Y])(f: (X, Y) => Z): mutable.LinkedHashMap[K, Z] =
xs.flatMap {
case (k, x) => ys.get(k).map(k -> f(x, _))
}
it gives me this:
val map1 = LinkedHashMap(4 -> (4), 7 -> (4,7))
val map2 = LinkedHashMap(3 -> (3), 6 -> (3,6), 7 -> (3,7))
val merged = mergeWith(map1,map2){ (x, y) => (x, y) }
merged: scala.collection.mutable.LinkedHashMap[Int,(Any, Any)] = Map(7 -> ((4,7),(3,7)))
But what i want is this:
merged: scala.collection.mutable.LinkedHashMap[Int,(Any, Any)] = Map(3 -> (3), 4 -> (4), 6 -> (3,6), 7 -> ((4,7),(3,7)))
How to modify my code to obtain it?
It can't be done with the current mergeWith() signature. In particular, you're trying to create a LinkedHashMap[K,Z] but there is no Z input. The only way to get a Z is to invoke f() which requires both X and Y as passed parameters.
So if xs is type LinkedHashMap[Int,Char] and has element (2 -> 'w'), and ys is type LinkedHashMap[Int,Long] and has element (8 -> 4L), how are you going to invoke f(c:Char, l:Long) so that you have a [K,Z] entry for both keys 2 and 8? Not possible.
If the mergeWith() signature can be simplified you might do something like this.
def mergeWith[K,V](xs: collection.mutable.LinkedHashMap[K, V]
,ys: collection.mutable.LinkedHashMap[K, V]
)(f: (V, V) => V): collection.mutable.LinkedHashMap[K,V] = {
val ns = collection.mutable.LinkedHashMap[K,V]()
(xs.keySet ++ ys.keySet).foreach{ k =>
if (!xs.isDefinedAt(k)) ns.update(k, ys(k))
else if (!ys.isDefinedAt(k)) ns.update(k, xs(k))
else ns.update(k, f(xs(k), ys(k)))
}
ns
}
This produces the desired result for the example you've given, but it has a number of undesirable qualities, not the least of which is the mutable data structures.
BTW, there is no such thing as a Tuple1 so (4) is the same thing as 4. And whenever you see type Any, it's a pretty good sign that your design needs a re-think.

Scala: Map keys with wildcards?

Is it possible to use keys with wildcards for Scala Maps? For example tuples of the form (x,_)? Example:
scala> val t1 = ("x","y")
scala> val t2 = ("y","x")
scala> val m = Map(t1 -> "foo", t2 -> "foo")
scala> m(("x","y"))
res5: String = foo
scala> m(("x",_))
<console>:11: error: missing parameter type for expanded function ((x$1) => scala.Tuple2("x", x$1))
m(("x",_))
^
It would be great if there was a way to retrieve all (composite_key, value) pares where only some part of composite key is defined. Other ways to get the same functionality in Scala?
How about use collect
Map( 1 -> "1" -> "11", 2 -> "2" -> "22").collect { case (k#(1, _), v ) => k -> v }
Using comprehensions like this:
for ( a # ((k1,k2), v) <- m if k1 == "x" ) yield a
In general, you can do something like
m.filter(m => (m._1 == "x"))
but in your particular example it will still return only one result, because a Map has only one entry per key. If your key itself is composite then it will indeed make more sense:
scala> Map((1,2)->"a", (1,3)->"b", (3,4)->"c").filter(m => (m._1._1 == 1))
res0: scala.collection.immutable.Map[(Int, Int),String] = Map((1,2) -> a, (1,3) -> b)
Think about what is happening under the hood of the Map. The default Map in Scala is scala.collection.immutable.HashMap, which stores things based on their hash codes. Do ("x", "y") and ("x", "y2") have hash codes that relate to each other in anyway? No, they don't, and their is no efficient way to implement wildcards with this map. The other answers provide solutions, but these will iterate over key/value pair in the entire Map, which is not efficient.
If you expect you are going to want to do operations like this, use a TreeMap. This doesn't use a hash table internally, put instead puts elements into a tree based on an ordering. This is similar to the way a relational database uses B-Trees for its indices. Your wildcard query is like using a two-column index to filter on the first column in the index.
Here is an example:
import scala.collection.immutable.TreeMap
val t1 = ("x","y")
val t2 = ("x","y2")
val t3 = ("y","x")
val m = TreeMap(t1 -> "foo1", t2 -> "foo2", t3 -> "foo3")
// "" is < than all other strings
// "x\u0000" is the next > string after "x"
val submap = m.from(("x", "")).to(("x\u0000", ""))
submap.values.foreach(println) // prints foo1, foo2

Update values of Map

I have a Map like:
Map("product1" -> List(Product1ObjectTypes), "product2" -> List(Product2ObjectTypes))
where ProductObjectType has a field usage. Based on the other field (counter) I have to update all ProductXObjectTypes.
The issue is that this update depends on previous ProductObjectType, and I can't find a way to get previous item when iterating over mapValues of this map. So basically, to update current usage I need: CurrentProduct1ObjectType.counter - PreviousProduct1ObjectType.counter.
Is there any way to do this?
I started it like:
val reportsWithCalculatedUsage =
reportsRefined.flatten.flatten.toList.groupBy(_._2.product).mapValues(f)
but I don't know in mapValues how to access previous list item.
I'm not sure if I understand completely, but if you want to update the values inside the lists based on their predecessors, this can generally be done with a fold:
case class Thing(product: String, usage: Int, counter: Int)
val m = Map(
"product1" -> List(Thing("Fnord", 10, 3), Thing("Meep", 0, 5))
//... more mappings
)
//> Map(product1 -> List(Thing(Fnord,10,3), Thing(Meep,0,5)))
m mapValues { list => list.foldLeft(List[Thing]()){
case (Nil, head) =>
List(head)
case (tail, head) =>
val previous = tail.head
val current = head copy (usage = head.usage + head.counter - previous.counter)
current :: tail
} reverse }
//> Map(product1 -> List(Thing(Fnord,10,3), Thing(Meep,2,5)))
Note that regular map is an unordered collection, you need to use something like TreeMap to have predictable order of iteration.
Anyways, from what I understand you want to get pairs of all values in a map. Try something like this:
scala> val map = Map(1 -> 2, 2 -> 3, 3 -> 4)
scala> (map, map.tail).zipped.foreach((t1, t2) => println(t1 + " " + t2))
(1,2) (2,3)
(2,3) (3,4)

How to convert Map[String,Seq[String]] to Map[String,String]

I have a Map[String,Seq[String]] and want to basically covert it to a Map[String,String] since I know the sequence will only have one value.
Someone else already mentioned mapValues, but if I were you I would do it like this:
scala> val m = Map(1 -> Seq(1), 2 -> Seq(2))
m: scala.collection.immutable.Map[Int,Seq[Int]] = Map(1 -> List(1), 2 -> List(2))
scala> m.map { case (k,Seq(v)) => (k,v) }
res0: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 2 -> 2)
Two reasons:
The mapValues method produces a view of the result Map, meaning that the function will be recomputed every time you access an element. Unless you plan on accessing each element exactly once, or you only plan on accessing a very small percentage of them, you don't want that recomputation to take place.
Using a case with (k,Seq(v)) ensures that an exception will be thrown if the function ever sees a Seq that doesn't contain exactly one element. Using _(0) or _.head will throw an exception if there are zero elements, but will not complain if you had more than one, which will likely result in mysterious bugs later on when things go missing without errors.
You can use mapValues().
scala> Map("a" -> Seq("aaa"), "b" -> Seq("bbb"))
res0: scala.collection.immutable.Map[java.lang.String,Seq[java.lang.String]] = M
ap(a -> List(aaa), b -> List(bbb))
scala> res0.mapValues(_(0))
res1: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(a
-> aaa, b -> bbb)
I think I got it by doing the following:
mymap.flatMap(x => Map(x._1 -> x._2.head))
Yet another suggestion:
m mapValues { _.mkString }
This one's agnostic to whether the Seq has multiple elements -- it'll just concatenate all the strings together. If you're concerned about the recomputation of each value, you can make it happen up-front:
(m mapValues { _.mkString }).view.force

Map with only certain keys

With regard to maps in Scala, if ms - (k, 1, m) returns the map containing all mappings of ms except for any mapping with the given keys, x, 1 and m.
Then, what statement will return a map of all mappings of ms with only the given keys, x, 1 and m. i.e. I'm looking for the subset of ms where only k, 1 and m are keys.
This works, but it is terrible:
scala> val originalMap = Map("age" -> "20", "name" -> "jack", "hobby" -> "jumping")
ms: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(age -> 20, name -> jack, hobby -> jumping)
scala> val interestingKeys = List("name", "hobby")
interesting: List[java.lang.String] = List(name, hobby)
scala> val notInterestingMap = originalMap -- interestingKeys
notInterestingMap: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(age -> 20)
scala> val interestingMap = originalMap -- notInterestingMap.keySet
interestingMap: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(name -> jack, hobby -> jumping)
filterKeyscan help:
scala> originalMap.filterKeys(interestingKeys.contains)
res0: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(name -> jack, hobby -> jumping)
Because filterKeys filters based on an arbitrary predicate, it has to consider every key in the map. This might be fine or not, depending on how large the map is, etc., but it's definitely not necessary for the operation you describe. I'd use something like the following:
interestingKeys.flatMap(k => originalMap.get(k).map((k, _))).toMap
This will be O(n) or O(n log m) depending on your map implementation (where n is the size of interestingKeys and m is the size of the map), instead of O(m log n) or O(mn).
If you really want your ~ operator, you can use the pimp-my-library pattern:
class RichMap[A, B](m: Map[A, B]) {
def ~(ks: A*) = ks.flatMap(k => m.get(k).map((k, _))).toMap
}
implicit def enrichMap[A, B](m: Map[A, B]) = new RichMap(m)
Now originalMap ~ ("name", "hobby") returns Map(name -> jack, hobby -> jumping), as you'd expect.
I think the original code is not that bad, and it can be easily transformed into a one-liner operating on key sets:
val interestingMap = originalMap -- (originalMap.keySet -- interestingKeys)
I find this quite readable.