Scala Map with mutable default value always point to the same object - scala

Scala version:3.1
I want to create a String -> Int map, that each key may point to many Int.
Therefore I choose map with default mutable Buffer[Int].
But it seems the all the keys always point to the same Buffer[Int]
Notice it is also weird in the first m("a") += 1 that, it print out nothing after this += but default value changed so that it print two 1 in the next println.
Also, how I can fix the problem that mutable.Map's += is overriden by Buffer's +=. I am expecting the map insert a new Buffer if the key doens't exist, then call += of the buffer.
import scala.collection.mutable
val m: mutable.Map[String, mutable.Buffer[Int]] =
mutable.HashMap.empty.withDefaultValue(mutable.Buffer.empty)
m("a") += 1
println(m) // Map()
m("a") = m("a") += 1
println(m) // Map(a -> ArrayBuffer(1, 1))
m("b") = m("b") += 2 // Map(a -> ArrayBuffer(1, 1, 2), b -> ArrayBuffer(1, 1, 2)) , not expecting this, key is correct but
println(m) // Map(a -> ArrayBuffer(1, 1, 2), b -> ArrayBuffer(1, 1, 2))
m("a") = m("a") += 2
println(m) // Map(a -> ArrayBuffer(1, 1, 2, 2), b -> ArrayBuffer(1, 1, 2, 2)) ,
// this is not as I expected. The keys are correct, however their values are all the same ArrayBuffer

TL;DR: withDefaultValue is useless here.
Note the signature of withDefaultValue is:
def withDefaultValue(d: V): Map[K, V]
The parameter d is taken by value, not by name (=> V), therefore it is only evaluated once. Therefore any key not present in the original map will return the same empty buffer you created once.
And what you get withDefaultValue (and withDefault) is a new map-like object, not the original map. Only assigning the results (m("a") = ...) will change it.
Note: The default is only used for apply. Other methods like get, contains, iterator, keys, etc. are not affected by withDefaultValue.
Consider this example:
val m = new HashMap[Integer, Buffer[String]]()
val mm = m.withDefault(_ => Buffer.empty)
mm(1).append("4")
mm(1) // ArrayBuffer()
What you probably want is getOrElseUpdate (you can define a helper function if you'd like) with the signature
getOrElseUpdate(key: K, defaultValue: => V): V
Note how defaultValue is called by name this time, and each call will create a new buffer:
val m: mutable.Map[String, mutable.Buffer[Int]] =
mutable.HashMap.empty
def mm(key: String): mutable.Buffer[Int] = m.getOrElseUpdate(key, mutable.Buffer.empty)
mm("a") += 1
mm("b") += 2
// now buffers at `a` and `b` are distinct

Related

How to move contents of one element in a map to another element in Scala

I am trying to transfer/copy an element in a map, to another element in the map in Scala. For example:
Map(0 -> 5)
Let's say this is the initial state of the map. What I want to happen is the following:
Map(0 -> 0, 1 -> 5)
So after the change has happened, 0 that initially points to 5, but after the transformation 0 will point to 0, and a new element is added (1) that points to 5.
I have tried the following:
theMap + (pointer -> (theMap(pointer) + 1))
However, I get the following error:
java.util.NoSuchElementException: key not found: 1
Thanks for any help!
This should do the trick.
def transfer(pointer: Int)(map: Map[Int, Int]): Map[Int, Int] =
map.get(key = pointer) match {
case Some(value) =>
map ++ Map(
pointer -> 0,
(pointer + 1) -> value
)
case None =>
// Pointer didn't exist, what should happen here?
map // For now returning the map unmodified.
}
And you can use it like this:
transfer(pointer = 0)(map = Map(0 -> 5))
// res: Map[Int,Int] = Map(0 -> 0, 1 -> 5)

Using span function to bisect a Map in Scala

I have a Map of the form [Int, Option[/* of some type that I am using */], thus:
scala> val t1: Map[Int,Option[(String,List[Int])]] = Map(500->Some("A",List(1,2,3)))
t1: Map[Int,Option[(String, List[Int])]] = Map(500 -> Some((A,List(1, 2, 3))))
scala> t1 + (400 -> Some("B",List(9,8,7))) + (300 -> None) + (200 -> None)
res6: scala.collection.immutable.Map[Int,Option[(String, List[Int])]] = Map(500 -> Some((A,List(1, 2, 3))), 400 -> Some((B,List(9, 8, 7))), 300 -> None, 200 -> None)
Now, I am trying to cleave into two maps, one having all the empty Values -from eponymous Key/Value - and the other having none of them, thus:
res6.span(e => e._2.isEmpty)
res7: (scala.collection.immutable.Map[Int,Option[(String, List[Int])]], scala.collection.immutable.Map[Int,Option[(String, List[Int])]]) = (Map(),Map(500 -> Some((A,List(1, 2, 3))), 400 -> Some((B,List(9, 8, 7))), 300 -> None, 200 -> None))
I am failing to understand why I am getting an empty Map on the left, while the < K,None > pairs are sitting blissfully inside the Map on the right. They should have been in the Map on the left, or so I expect.
What is the obvious thing that I am missing?
You should use partition instead of span.
Note: c span p is equivalent to (but possibly more efficient than) (c takeWhile p, c dropWhile p), provided the evaluation of the predicate p does not cause any side-effects.
So, span would stop scanning if the condition is not met.
For example,
scala> val l = List(1, 9, 8, 0)
scala> l.span(e => e < 2)
res7: (List[Int], List[Int]) = (List(1),List(9, 8, 0))
scala> l.partition(e => e < 2)
res8: (List[Int], List[Int]) = (List(1, 0),List(9, 8))
Note that, actually for span, it might return different results for different runs, unless the underlying collection type is ordered.
In your case, the first element in map may not None. (Map is not ordered)
By definition when you use span we get a Tuple2 of sequences that are of the same type as the original collection, one contain true values and other false values.
def span(p: A => Boolean): (Repr, Repr) =
In your case
res6.span(e => e._2.isEmpty)
So in your case you have a empty and non-empty elements of Tuple2.
If you wish to get non-empty values you use simply use _2 as
val nonEmptyValue = res6.span(e => e._2.isEmpty)._2

How to add all the values of a map without using recurrsion or var

I want to add all the values in a map without using var or any mutable structures. I have tried to do something like this but it doens't work:
val mymap = ("a" -> 1, "b" -> 2)
val sum_of_alcohol_consumption =
for ((k,v) <- mymap ) yield (sum_of_alcohol_consumption += v)
I have been told that I can use .sum on a list
Please help
Thanks
You can use the .values function of a Map to return an Iterable List of its values (all of the Integers) and then call the .sum function on that:
val myMap = Map("a" -> 1, "b" -> 2)
val sum = myMap.values.sum
println(sum) // Outputs: 3
An equivalent answer to the more elegant use of sum is to use a fold operation. sum is implemented in a manner similar to this:
val myMap = Map("a" -> 1, "b" -> 2)
val sumAlcoholConsumption = myMap.values.foldLeft(0)(_ + _)
values returns a sequence of only the values in the map. The first foldLeft argument is the zero value (think of it as the initial value for an accumulator value) for the operation. The second argument is a function that adds the current value of the accumulator to the current element, returning the sum of the two values - and it is applied to each value in turn. That said, sum is a lot more convenient.
To get the only values of map, it provides a function values which will return iterable,we can directly appy sum function to it.
scala> val mymap = Map("a" -> 1, "b" -> 2)
mymap: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2)
scala> mymap.values.sum
res7: Int = 3

Custom ordering in TreeMap

Here are examples that I have been playing with:
import collection.immutable.{TreeSet, TreeMap}
val ts = TreeSet(9, 23, 1, 2)
ts
val tm = TreeMap(3 -> "c", 1 -> "a", 2 -> "b")
tm
// convert a map to a sorted map
val m = Map("98" -> List(4, 12, 14), "001" -> List(22, 11))
val t = TreeMap(m.toSeq: _*)
t // sorted by key
// sort an unsorted map
m.toSeq.sortWith((x, y) => x._2(0) < y._2(0))
// add a unsorted map into a sorted map
val m1 = Map("07" -> List(3, 5, 1), "05" -> List(12, 5, 3))
val t1: TreeMap[String, List[Int]] = t ++ m1
t1 // "001" is the first key
I can use sortWith on a Map to get a custom ordering, what if I want to use a TreeMap that uses a different ordering than the default?
You can't use Map's values to define default ordering of a Map.
TreeMap[A,B]'s constructor accepts an implicit Ordering[A] parameter, so you could do something like this:
// Will sort according to default Int ordering (ascending by numeric value)
scala> val tm = TreeMap(3 -> "c", 1 -> "a", 2 -> "b")
tm: scala.collection.immutable.TreeMap[Int,String] = Map(1 -> a, 2 -> b, 3 -> c)
// A wild implicit appears! (orders descending by numeric value)
scala> implicit val tmOrd = Ordering[Int].on((x:Int) => -x)
tmOrd: scala.math.Ordering[Int] = scala.math.Ordering$$anon$5#1d8e2eea
// Our implicit is implicitly (yeah) used by constructor
scala> val invTm = TreeMap(3 -> "c", 1 -> "a", 2 -> "b")
invTm: scala.collection.immutable.TreeMap[Int,String] = Map(3 -> c, 2 -> b, 1 -> a)
Note that it's safer to limit a scope of implicits like this one. If you can, you should construct an (not-implicit) object and pass it manually, or separate the scope of implicit declaration from the place where other code can be affected by its presence.
The reason behind this is that TreeMap is built on top of a tree that uses keys' values to maintain structure constraints that allow for efficient data reads/writes based on keys, which is the primary purpose of a Map. Ordering on values in a Map simply makes no sense.
Upd.: The complexity of ordering logic doesn't mean anything. According to your comment:
scala> object ComplexOrdering extends Ordering[Int] {
| def compare(a: Int, b: Int) = {
| if(a == 3) -1 else if(a == 2 * b) -1 else if(a == 3 * b) 0 else 1
| }
| }
defined object ComplexOrdering
scala> val tm = TreeMap(3 -> "c", 1 -> "a", 2 -> "b")
tm: scala.collection.immutable.TreeMap[Int,String] = Map(1 -> a, 2 -> b, 3 -> c)
scala> val tm = TreeMap(3 -> "c", 1 -> "a", 2 -> "b")(ComplexOrdering)
tm: scala.collection.immutable.TreeMap[Int,String] = Map(3 -> c, 2 -> b, 1 -> a)
TreeMap is defined as a Map-like type with a specified ordering of its keys. That ordering is given by an implicit parameter to the constructor:
new TreeMap()(implicit ordering: Ordering[A]) // For TreeMap[A,B]
so you can set an alternative ordering on the keys at construction by explicitly providing a custom Ordering[A].
The class does not, however, provide any (direct) means of setting an ordering based on the values. What you have with calling .toSeq.sortWith is about the best you can do as far as I know, short of coding your own collection type.

Multi-key Map in Scala

How can I create a Map in Scala which does not only take a single parameter as key, but rather two or three.
val map = //..?
map("abc", 1) = 1
println(map("abc", 2)) // => null
println(map("abc", 1)) // => 1
I tried using tuples as a key, but then I have to assign values like this
map(("abc", 1)) = 1
Can I somehow get rid of the inner parentheses?
You could also use
map += ("abc", 1) -> 1
If the map key represents something (e.g. user info) and if you want to add clarity to your code (especially if you have 3 elements in the key), I would go with a case class as the key. Case classes have equals and hashcode implemented so you can safely use them as keys in a map. The code would be more verbose though:
case class MapKey(s: String, i: Int, d: Double)
val map = Map[MapKey, X](MapKey("a", 1, 1.1) -> "value1", MapKey("b", 2, 2.2) -> "value2")
val map2 = map + (MapKey("c", 3, 3.3) -> "value3")
//or for mutable map
map(MapKey("d", 4, 4.4)) = "value4"
//or
map += MapKey("e", 5, 5.5) -> "value5"
You can add your own enhancement to Map that will do the trick:
import collection.mutable.Map
implicit class EnhancedMap[A,B,C](m: Map[(A,B),C]) {
def update(a: A, b: B, c: C) { m((a,b)) = c }
}
then
val map = Map(("abc", 1) -> 0)
map("abc", 1) = 1
works just fine.
You can use -> syntax for tuples:
map("abc" -> 1) = 1
I got compiling errors using Luigi Plinge's approach. The following approach works for me, which is simpler.
scala> var b = Map[(Int, Int), Int]()
b: scala.collection.mutable.Map[(Int, Int),Int] = Map()
scala> b = b + ((1,1)->2)
b: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,1) -> 2)
scala> b
res15: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,1) -> 2)
scala> b = b + ((1,2)->2)
b: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,1) -> 2, (1,2) -> 2)
scala> b(1,1)
res16: Int = 2
scala> b(1,2)
res17: Int = 2