Create seq of maps from two lists with specified keys - scala

I have two lists, one containing string elements and the other containing int elements, say
foo = ["In", "Out"] and bar = [10, 6].
I want to create a sequence of maps which maps foo elements to "Key" and bar elements to "Value" such that
Seq(Map("Key" -> "In", "Value" -> 10), Map("Key" -> "Out", "Value" -> 6))
How could I achieve this?

You can zip both the lists and then map over the combined list to create desired Seq
val lst = List("In", "Out")
val lst2 = List(10, 6)
lst.zip(lst2).map(x=> Map("key" -> x._1,"value"-> x._2))
//output
//List(Map(key -> In, value -> 10), Map(key -> Out, value -> 6))

Related

Transform Sequence of Maps into Map of Keys to Sequences of Values in Scala

I'm trying to turn a sequence of maps, like for instance
val input = Seq(Map("a" -> 1, "b" -> 2),
Map("a" -> 10, "c" -> 30),
Map("b" -> 200, "c" -> 300, "d" -> 400))
into a map from the keys in those maps to the sequence of values they map to across each of the maps in the original sequence.
So the above should transform into
val output = Map("a" -> Seq(1, 10),
"b" -> Seq(2, 200),
"c" -> Seq(30, 300),
"d" -> Seq(400))
I can think of a few ways to go about this but I'm looking for a solution that is idiomatic or the best scala style for this sort of transformation? Ideally without being really wasteful but performance isn't a great concern.
Thanks!
You can do this:
val output = input.flatten.groupBy(_._1).mapValues(_.map(_._2))
It will first flatten your map into a Seq of all map entries. Then it groups the map entries on their keys. Finally, you map the values (which is now a list of map entries) to a list of map-values.

Invert a Map (String -> List) in Scala

I have a Map[String, List[String]] and I want to invert it. For example, if I have something like
"1" -> List("a","b","c")
"2" -> List("a","j","k")
"3" -> List("a","c")
The result should be
"a" -> List("1","2","3")
"b" -> List("1")
"c" -> List("1","3")
"j" -> List("2")
"k" -> List("2")
I've tried this:
m.map(_.swap)
But it returns a Map[List[String], String]:
List("a","b","c") -> "1"
List("a","j","k") -> "2"
List("a","c") -> "3"
Map inversion is a little more complicated.
val m = Map("1" -> List("a","b","c")
,"2" -> List("a","j","k")
,"3" -> List("a","c"))
m flatten {case(k, vs) => vs.map((_, k))} groupBy (_._1) mapValues {_.map(_._2)}
//res0: Map[String,Iterable[String]] = Map(j -> List(2), a -> List(1, 2, 3), b -> List(1), c -> List(1, 3), k -> List(2))
Flatten the Map into a collection of tuples. groupBy will create a new Map with the old values as the new keys. Then un-tuple the values by removing the key (previously value) elements.
An alternative that does not rely on strange implicit arguments of flatten, as requested by yishaiz:
val m = Map(
"1" -> List("a","b","c"),
"2" -> List("a","j","k"),
"3" -> List("a","c"),
)
val res = (for ((digit, chars) <- m.toList; c <- chars) yield (c, digit))
.groupBy(_._1) // group by characters
.mapValues(_.unzip._2) // drop redundant digits from lists
res foreach println
gives:
(j,List(2))
(a,List(1, 2, 3))
(b,List(1))
(c,List(1, 3))
(k,List(2))
A simple nested for-comprehension may be used to invert the map in such a way that each value in the List of values are keys in the inverted map with respective keys as their values
implicit class MapInverter[T] (map: Map[T, List[T]]) {
def invert: Map[T, T] = {
val result = collection.mutable.Map.empty[T, T]
for ((key, values) <- map) {
for (v <- values) {
result += (v -> key)
}
}
result.toMap
}
Usage:
Map(10 -> List(3, 2), 20 -> List(16, 17, 18, 19)).invert

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) })

scala map += operator with five pairs

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/

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)