scala map += operator with five pairs - scala

I am having an issue with appending pairs to an existing Map. Once I reach the fifth pair of the Map, the Map reorders itself. The order is correct with 4 pairs, but as soon as the 5th is added it shifts itself. See example below (assuming I built the 4 pair Map one pair at a time.):
scala> val a = Map("a1" -> 1, "a2" -> 1, "a3" -> 1, "a4" -> 1)
a: scala.collection.immutable.Map[String,Int] = Map(a1 -> 1, a2 -> 1, a3 -> 1, a4 -> 1)
scala> a += ("a5" -> 1)
scala> a
res26: scala.collection.immutable.Map[String,Int] = Map(a5 -> 1, a4 -> 1, a3 -> 1, a1 -> 1, a2 -> 1)
The added fifth element jumped to the front of the Map and shifts the others around. Is there a way to keep the elements in order (1, 2, 3, 4, 5) ?
Thanks

By default Scala's immutable.Map uses HashMap.
From http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html:
This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time
So a map is really not a table that contains "a1" -> 1, but a table that contains hash("a1") -> 1. The map reorders its keys based on the hash of the key rather than the key you put in it.
As was recommended in the comments, use LinkedHashMap or ListMap:
Scala Map implementation keeping entries in insertion order?
PS: You might be interested in reading this article: http://howtodoinjava.com/2012/10/09/how-hashmap-works-in-java/

Related

Scala - reducing a map based on changed key

Lets say have the following Map data
val testMap: Map[String, Int] = Map("AAA_abc" -> 1,
"AAA_anghesh" -> 2,
"BBB_wfejw" -> 3,
"BBB_qgqwe" -> 4,
"C_fkee" -> 5)
Now I want to reduce the map by key.split("_").head and add all the values for the keys that became equal. So for this example the Map should result into:
Map(AAA -> 3, BBB -> 7, C -> 5)
What would be the correct way to do so in Scala?
I tried constructions with groupBy and reduceLeft but could not find a solution.
Here's a way to do it:
testMap.groupBy(_._1.split("_").head).mapValues(_.values.sum)
A variation in one pass:
testMap.foldLeft(Map[String,Int]())( (map, kv) => {
val key = kv._1.split("_").head
val previous = map.getOrElse(key,0)
map.updated(key, previous + kv._2) })

how to union the element whose type is map of a vector to a map in scala?

I have a vector of Maps as below, how to convert them into one map?
scala> (1 to 100).takeWhile(_<10).map{x=>val y=x+1;Map(x->y)}
res8: scala.collection.immutable.IndexedSeq[scala.collection.immutable.Map[Int,Int]] = Vector(Map(1 -> 2), Map(2 -> 3), Map(3 -> 4), Map(4 -> 5), Map(5 -> 6), Map(6 -> 7), Map(7 -> 8), Map(8 -> 9), Map(9 -> 10))
If you do not need to turn each element into a map, then the tuples can go straight to a Map like this
(1 to 100).takeWhile(_<10).map{x=>val y=x+1;x->y}.toMap
If you do have to go from a Seq of maps, as shown in the question then fold may be used to join the maps together
val v = (1 to 100).takeWhile(_<10).map{x=>val y=x+1;Map(x->y)}
v.fold(Map.empty)((a,b) => a ++ b )
Fold works by starting with an initial value, in this case Map.empty and then performing an operation on that value and then keeping the result of that op to be used with the next element of the sequence. It then repeats for every element in the sequence. In the example that I have given, the operation was (a,b) => a ++ b, where a starts as the initial value and then is the result of each iteration and b is the current element being considered from the sequence being folded over.

will using "+" preserve my order in a map ? No right?

scala> var test2 : Map[String , String] = Map("a"->"b","c"->"d")
test2: Map[String,String] = Map(a -> b, c -> d)
test2 = test2 + ("e"->"f" , "g"->"h")
test2: Map[String,String] = Map(a -> b, c -> d, e -> f, g -> h)
And so on. I want to know that Map is not supposed to preserve order of insertion [For that purpose we have LinkedHashMap]then why are the results showing preservation of order? is this a mere coincidence or there is more than meets the eye ?
Thanks in advance!
It's coincidence that holds only for the first 4 items.
val m = Map('a' -> 1, 'b' -> 2, 'c' -> 3, 'd' -> 4)
// m: immutable.Map[Char,Int] = Map(a -> 1, b -> 2, c -> 3, d -> 4)
m + ('e' -> 5)
// immutable.Map[Char,Int] = Map(e -> 5, a -> 1, b -> 2, c -> 3, d -> 4)
The reason is that there is a special optimized implementations for small maps which indeed preserve insertion order (e.g. append to Map of one pair), but once you cross this border it doesn't work anymore.
Coincidental. The ordering may be preserved, but preservation of ordering is not guaranteed and should not be relied upon. In fact, one should not consider maps to be ordered at all.

toMap when keys are repeated with different values

I have a list
val data = List(2, 4, 3, 2, 1, 1, 1,7)
with which I want to create a map such that values in above list are keys to new one with indeces as new values I tried
scala> data.zipWithIndex.toMap
res5: scala.collection.immutable.Map[Int,Int] = Map(1 -> 6, 2 -> 3, 7 -> 7, 3 -> 2, 4 -> 1)
but strangely it gives res5(1) as 6 but I want it to be 4.
I could solve it by
data.zipWithIndex groupBy (_._1) mapValues (w=>w.map(tuple=>tuple._2) min)
but is there any way I can pass a function f to toMap so that it creates map in desired way.
toMap is going to add each pair to the map in the order of the zipped list, and when you add a mapping k -> v to a map that already contains a k, the old value is simply replaced.
An easy fix is just to reverse the list after zipping the indices and before converting to a map:
data.zipWithIndex.reverse.toMap
Now the mappings 1 -> 6 and 1 -> 5 will be added before 1 -> 4, which means 1 -> 4 is the one you'll see in the result.

Select first 'N' elements from map in Scala

Is there an elegant method of extracting first 'N' elements from a Map ?
I could create a new Map and iterate over the values that are to be selected, is there a function that accomplishes this ?
From the docs for the take method on Map:
Selects first n elements.
Note: might return different results for different runs, unless the
underlying collection type is ordered.
In the case of maps the collection isn't ordered, so don't count on getting the first n elements—in fact the concept of the first n elements doesn't even exist for maps.
But take will give you some first n elements, and it sounds like this is what you want:
scala> Map('a -> 1, 'b -> 2, 'c -> 3).take(2)
res1: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 1, 'b -> 2)
In this case you happen to get the two elements that came first in the definition, but don't count on this happening.
Sounds like you're looking for a SortedMap, along with take(n) as discussed by others.
scala> val map = Map[String,Int]("one"->1,"two"->2,"three"->3)
map: scala.collection.immutable.Map[String,Int] =
Map(one -> 1, two -> 2, three -> 3)
scala> val n = 2
n: Int = 2
scala> val firstN = map.take(n)
firstN: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)