Related
This question already has answers here:
Elegant way to invert a map in Scala
(10 answers)
Closed 3 years ago.
I have:
val intsPerChar: List[(Char, List[Int])] = List(
'A' -> List(1,2,3),
'B' -> List(2,3)
)
I want to get a mapping of ints with the chars that they have a mapping with. ie, I want to get:
val charsPerInt: Map[Int, List[Char]] = Map(
1 -> List('A'),
2 -> List('A', 'B'),
3 -> List('A', 'B')
)
Currently, I am doing the following:
val numbers: List[Int] = l.flatMap(_._2).distinct
numbers.map( n =>
n -> l.filter(_._2.contains(n)).map(_._1)
).toMap
Is there a less explicit way of doing this? ideally some sort of groupBy.
Try
intsPerChar
.flatMap { case (c, ns) => ns.map((_, c)) }
.groupBy(_._1)
.mapValues(_.map(_._2))
// Map(2 -> List(A, B), 1 -> List(A), 3 -> List(A, B))
Might be personal preference as to whether you consider it more or less readable, but the following is another option:
intsPerChar
.flatMap(n => n._2.map(i => i -> n._1)) // List((1,A), (2,A), (3,A), (2,B), (3,B))
.groupBy(_._1) // Map(2 -> List((2,A), (2,B)), 1 -> List((1,A)), 3 -> List((3,A), (3,B)))
.transform { (_, v) => v.unzip._2}
Final output is:
Map(2 -> List(A, B), 1 -> List(A), 3 -> List(A, B))
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
I want to merge them, and sum the values of same keys. So the result will be:
Map(2->20, 1->109, 3->300)
Now I have 2 solutions:
val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }
and
val merged = (map1 /: map2) { case (map, (k,v)) =>
map + ( k -> (v + map.getOrElse(k, 0)) )
}
But I want to know if there are any better solutions.
The shortest answer I know of that uses only the standard library is
map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Scalaz has the concept of a Semigroup which captures what you want to do here, and leads to arguably the shortest/cleanest solution:
scala> import scalaz._
import scalaz._
scala> import Scalaz._
import Scalaz._
scala> val map1 = Map(1 -> 9 , 2 -> 20)
map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 9, 2 -> 20)
scala> val map2 = Map(1 -> 100, 3 -> 300)
map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 100, 3 -> 300)
scala> map1 |+| map2
res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 109, 3 -> 300, 2 -> 20)
Specifically, the binary operator for Map[K, V] combines the keys of the maps, folding V's semigroup operator over any duplicate values. The standard semigroup for Int uses the addition operator, so you get the sum of values for each duplicate key.
Edit: A little more detail, as per user482745's request.
Mathematically a semigroup is just a set of values, together with an operator that takes two values from that set, and produces another value from that set. So integers under addition are a semigroup, for example - the + operator combines two ints to make another int.
You can also define a semigroup over the set of "all maps with a given key type and value type", so long as you can come up with some operation that combines two maps to produce a new one which is somehow the combination of the two inputs.
If there are no keys that appear in both maps, this is trivial. If the same key exists in both maps, then we need to combine the two values that the key maps to. Hmm, haven't we just described an operator which combines two entities of the same type? This is why in Scalaz a semigroup for Map[K, V] exists if and only if a Semigroup for V exists - V's semigroup is used to combine the values from two maps which are assigned to the same key.
So because Int is the value type here, the "collision" on the 1 key is resolved by integer addition of the two mapped values (as that's what Int's semigroup operator does), hence 100 + 9. If the values had been Strings, a collision would have resulted in string concatenation of the two mapped values (again, because that's what the semigroup operator for String does).
(And interestingly, because string concatenation is not commutative - that is, "a" + "b" != "b" + "a" - the resulting semigroup operation isn't either. So map1 |+| map2 is different from map2 |+| map1 in the String case, but not in the Int case.)
Quick solution:
(map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap
Well, now in scala library (at least in 2.10) there is something you wanted - merged function. BUT it's presented only in HashMap not in Map. It's somewhat confusing. Also the signature is cumbersome - can't imagine why I'd need a key twice and when I'd need to produce a pair with another key. But nevertheless, it works and much cleaner than previous "native" solutions.
val map1 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
val map2 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
map1.merged(map2)({ case ((k,v1),(_,v2)) => (k,v1+v2) })
Also in scaladoc mentioned that
The merged method is on average more performant than doing a
traversal and reconstructing a new immutable hash map from
scratch, or ++.
This can be implemented as a Monoid with just plain Scala. Here is a sample implementation. With this approach, we can merge not just 2, but a list of maps.
// Monoid trait
trait Monoid[M] {
def zero: M
def op(a: M, b: M): M
}
The Map based implementation of the Monoid trait that merges two maps.
val mapMonoid = new Monoid[Map[Int, Int]] {
override def zero: Map[Int, Int] = Map()
override def op(a: Map[Int, Int], b: Map[Int, Int]): Map[Int, Int] =
(a.keySet ++ b.keySet) map { k =>
(k, a.getOrElse(k, 0) + b.getOrElse(k, 0))
} toMap
}
Now, if you have a list of maps that needs to be merged (in this case, only 2), it can be done like below.
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
val maps = List(map1, map2) // The list can have more maps.
val merged = maps.foldLeft(mapMonoid.zero)(mapMonoid.op)
map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) )
I wrote a blog post about this , check it out :
http://www.nimrodstech.com/scala-map-merge/
basically using scalaz semi group you can achieve this pretty easily
would look something like :
import scalaz.Scalaz._
map1 |+| map2
You can also do that with Cats.
import cats.implicits._
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
map1 combine map2 // Map(2 -> 20, 1 -> 109, 3 -> 300)
Starting Scala 2.13, another solution only based on the standard library consists in replacing the groupBy part of your solution with groupMapReduce which (as its name suggests) is an equivalent of a groupBy followed by mapValues and a reduce step:
// val map1 = Map(1 -> 9, 2 -> 20)
// val map2 = Map(1 -> 100, 3 -> 300)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_+_)
// Map[Int,Int] = Map(2 -> 20, 1 -> 109, 3 -> 300)
This:
Concatenates the two maps as a sequence of tuples (List((1,9), (2,20), (1,100), (3,300))). For conciseness, map2 is implicitly converted to Seq to adapt to the type of map1.toSeq - but you could choose to make it explicit by using map2.toSeq,
groups elements based on their first tuple part (group part of groupMapReduce),
maps grouped values to their second tuple part (map part of groupMapReduce),
reduces mapped values (_+_) by summing them (reduce part of groupMapReduce).
Andrzej Doyle's answer contains a great explanation of semigroups which allows you to use the |+| operator to join two maps and sum the values for matching keys.
There are many ways something can be defined to be an instance of a typeclass, and unlike the OP you might not want to sum your keys specifically. Or, you might want to do operate on a union rather than an intersection. Scalaz also adds extra functions to Map for this purpose:
https://oss.sonatype.org/service/local/repositories/snapshots/archive/org/scalaz/scalaz_2.11/7.3.0-SNAPSHOT/scalaz_2.11-7.3.0-SNAPSHOT-javadoc.jar/!/index.html#scalaz.std.MapFunctions
You can do
import scalaz.Scalaz._
map1 |+| map2 // As per other answers
map1.intersectWith(map2)(_ + _) // Do things other than sum the values
The fastest and simplest way:
val m1 = Map(1 -> 1.0, 3 -> 3.0, 5 -> 5.2)
val m2 = Map(0 -> 10.0, 3 -> 3.0)
val merged = (m2 foldLeft m1) (
(acc, v) => acc + (v._1 -> (v._2 + acc.getOrElse(v._1, 0.0)))
)
By this way, each of element's immediately added to map.
The second ++ way is:
map1 ++ map2.map { case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Unlike the first way, In a second way for each element in a second map a new List will be created and concatenated to the previous map.
The case expression implicitly creates a new List using unapply method.
Here's what I ended up using:
(a.toSeq ++ b.toSeq).groupBy(_._1).mapValues(_.map(_._2).sum)
This is what I came up with...
def mergeMap(m1: Map[Char, Int], m2: Map[Char, Int]): Map[Char, Int] = {
var map : Map[Char, Int] = Map[Char, Int]() ++ m1
for(p <- m2) {
map = map + (p._1 -> (p._2 + map.getOrElse(p._1,0)))
}
map
}
Using the typeclass pattern, we can merge any Numeric type:
object MapSyntax {
implicit class MapOps[A, B](a: Map[A, B]) {
def plus(b: Map[A, B])(implicit num: Numeric[B]): Map[A, B] = {
b ++ a.map { case (key, value) => key -> num.plus(value, b.getOrElse(key, num.zero)) }
}
}
}
Usage:
import MapSyntax.MapOps
map1 plus map2
Merging a sequence of maps:
maps.reduce(_ plus _)
I've got a small function to do the job, it's in my small library for some frequently used functionality which isn't in standard lib.
It should work for all types of maps, mutable and immutable, not only HashMaps
Here is the usage
scala> import com.daodecode.scalax.collection.extensions._
scala> val merged = Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _)
merged: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4)
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
And here's the body
def mergedWith(another: Map[K, V])(f: (V, V) => V): Repr =
if (another.isEmpty) mapLike.asInstanceOf[Repr]
else {
val mapBuilder = new mutable.MapBuilder[K, V, Repr](mapLike.asInstanceOf[Repr])
another.foreach { case (k, v) =>
mapLike.get(k) match {
case Some(ev) => mapBuilder += k -> f(ev, v)
case _ => mapBuilder += k -> v
}
}
mapBuilder.result()
}
https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190
For anyone coming across an AnyVal error, convert the values as follows.
Error:
"could not find implicit value for parameter num: Numeric[AnyVal]"
(m1.toSeq ++ m2.toSeq).groupBy(_._1).mapValues(_.map(_._2.asInstanceOf[Number].intValue()).sum)
When trying to remove all Unit - () from a list, I tried to call toMap.
scala> List((), ()).filter(_ != ()).toMap
<console>:8: error: Cannot prove that Unit <:< (T, U).
List((), ()).filter(_ != ()).toMap
^
What does this error mean?
For a List, I'd like to create a map of all tuples (String, String) for non-Unit elements, but some of the values can be null.
scala> val x = List((), (), (3,4)).filter(_ != ()).toMap
<console>:7: error: Cannot prove that Any <:< (T, U).
val x = List((), (), (3,4)).filter(_ != ()).toMap
^
scala> val x = List((), (), (3,4)).filter(_ != ())
x: List[Any] = List((3,4))
scala> x.toMap
<console>:9: error: Cannot prove that Any <:< (T, U).
x.toMap
^
Ah! Now your other question makes a little more sense. Still not sure what you're doing to produce this mixed Unit/Tuple2 list though.
This should work:
List((), (), (3,4)).collect { case t#(_: Int, _: Int) => t }.toMap
Note that I'm using variable binding here (binding the match to t) to return the same Tuple2 instance we matched rather than creating a new one.
By using collect you convert the type of your list from List[Any] to List[(Int, Int)], which is what toMap wants since it's expecting some List[(A,B)].
Note: Although this answer should work for you, I still think your design is flawed. You'd be better off fixing the underlying design flaw rather than treating the symptoms like this.
It looks like this would be a good fit for using Scala's Option type. In this case, your sample list would become List(None, None, Some((3,4))), or you could write it as List(None, None, Some(3->4)) for readability (nested parenthesis like that can get confusing).
If you use Option then the type of your list becomes List[Option[(Int, Int)]], which should be much nicer to deal with than a List[Any]. To get rid of the None entries and get the desired List[(Int,Int)] you can just call flatten:
List(None, None, Some(3->4)).flatten
// res0: List[(Int, Int)] = List((3,4))
List(None, None, Some(3->4)).flatten.toMap
// res1: scala.collection.immutable.Map[Int,Int] = Map(3 -> 4)
However, it would be even better if you can avoid putting the None entries in your list in the first place. If you're producing this list using a Scala for comprehension, you could use a guard in your for expression to remove the invalid elements from the output.
It means that the type of an element in the list can't be viewed as a tuple which is required to build a Map. A Map in a sense is a collection of tuples (and more).
Illustration:
scala> List(1).toMap
<console>:8: error: Cannot prove that Int <:< (T, U).
List(1).toMap
^
scala> List(1 -> 2).toMap
res1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)
I can build a map from a list of tuples, but not from a list of single cardinality elements.
Maybe you mean to say .map instead of .toMap? ;)
All in one go:
scala> val l2 = List(1 -> 3, (), 4 -> 4, (), 9 -> 4, (), 16 -> 7)
l2: List[Any] = List((1,3), (), (4,4), (), (9,4), (), (16,7))
scala> (l2 collect { case (a, b) => (a, b) }).toMap
res4: scala.collection.immutable.Map[Any,Any] = Map(1 -> 3, 4 -> 4, 9 -> 4, 16 -> 7)
Better typed:
scala> (l2 collect { case (i: Int, j: Int) => (i, j) }).toMap
res5: scala.collection.immutable.Map[Int,Int] = Map(1 -> 3, 4 -> 4, 9 -> 4, 16 -> 7)
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
I want to merge them, and sum the values of same keys. So the result will be:
Map(2->20, 1->109, 3->300)
Now I have 2 solutions:
val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }
and
val merged = (map1 /: map2) { case (map, (k,v)) =>
map + ( k -> (v + map.getOrElse(k, 0)) )
}
But I want to know if there are any better solutions.
The shortest answer I know of that uses only the standard library is
map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Scalaz has the concept of a Semigroup which captures what you want to do here, and leads to arguably the shortest/cleanest solution:
scala> import scalaz._
import scalaz._
scala> import Scalaz._
import Scalaz._
scala> val map1 = Map(1 -> 9 , 2 -> 20)
map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 9, 2 -> 20)
scala> val map2 = Map(1 -> 100, 3 -> 300)
map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 100, 3 -> 300)
scala> map1 |+| map2
res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 109, 3 -> 300, 2 -> 20)
Specifically, the binary operator for Map[K, V] combines the keys of the maps, folding V's semigroup operator over any duplicate values. The standard semigroup for Int uses the addition operator, so you get the sum of values for each duplicate key.
Edit: A little more detail, as per user482745's request.
Mathematically a semigroup is just a set of values, together with an operator that takes two values from that set, and produces another value from that set. So integers under addition are a semigroup, for example - the + operator combines two ints to make another int.
You can also define a semigroup over the set of "all maps with a given key type and value type", so long as you can come up with some operation that combines two maps to produce a new one which is somehow the combination of the two inputs.
If there are no keys that appear in both maps, this is trivial. If the same key exists in both maps, then we need to combine the two values that the key maps to. Hmm, haven't we just described an operator which combines two entities of the same type? This is why in Scalaz a semigroup for Map[K, V] exists if and only if a Semigroup for V exists - V's semigroup is used to combine the values from two maps which are assigned to the same key.
So because Int is the value type here, the "collision" on the 1 key is resolved by integer addition of the two mapped values (as that's what Int's semigroup operator does), hence 100 + 9. If the values had been Strings, a collision would have resulted in string concatenation of the two mapped values (again, because that's what the semigroup operator for String does).
(And interestingly, because string concatenation is not commutative - that is, "a" + "b" != "b" + "a" - the resulting semigroup operation isn't either. So map1 |+| map2 is different from map2 |+| map1 in the String case, but not in the Int case.)
Quick solution:
(map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap
Well, now in scala library (at least in 2.10) there is something you wanted - merged function. BUT it's presented only in HashMap not in Map. It's somewhat confusing. Also the signature is cumbersome - can't imagine why I'd need a key twice and when I'd need to produce a pair with another key. But nevertheless, it works and much cleaner than previous "native" solutions.
val map1 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
val map2 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
map1.merged(map2)({ case ((k,v1),(_,v2)) => (k,v1+v2) })
Also in scaladoc mentioned that
The merged method is on average more performant than doing a
traversal and reconstructing a new immutable hash map from
scratch, or ++.
This can be implemented as a Monoid with just plain Scala. Here is a sample implementation. With this approach, we can merge not just 2, but a list of maps.
// Monoid trait
trait Monoid[M] {
def zero: M
def op(a: M, b: M): M
}
The Map based implementation of the Monoid trait that merges two maps.
val mapMonoid = new Monoid[Map[Int, Int]] {
override def zero: Map[Int, Int] = Map()
override def op(a: Map[Int, Int], b: Map[Int, Int]): Map[Int, Int] =
(a.keySet ++ b.keySet) map { k =>
(k, a.getOrElse(k, 0) + b.getOrElse(k, 0))
} toMap
}
Now, if you have a list of maps that needs to be merged (in this case, only 2), it can be done like below.
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
val maps = List(map1, map2) // The list can have more maps.
val merged = maps.foldLeft(mapMonoid.zero)(mapMonoid.op)
map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) )
I wrote a blog post about this , check it out :
http://www.nimrodstech.com/scala-map-merge/
basically using scalaz semi group you can achieve this pretty easily
would look something like :
import scalaz.Scalaz._
map1 |+| map2
You can also do that with Cats.
import cats.implicits._
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
map1 combine map2 // Map(2 -> 20, 1 -> 109, 3 -> 300)
Starting Scala 2.13, another solution only based on the standard library consists in replacing the groupBy part of your solution with groupMapReduce which (as its name suggests) is an equivalent of a groupBy followed by mapValues and a reduce step:
// val map1 = Map(1 -> 9, 2 -> 20)
// val map2 = Map(1 -> 100, 3 -> 300)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_+_)
// Map[Int,Int] = Map(2 -> 20, 1 -> 109, 3 -> 300)
This:
Concatenates the two maps as a sequence of tuples (List((1,9), (2,20), (1,100), (3,300))). For conciseness, map2 is implicitly converted to Seq to adapt to the type of map1.toSeq - but you could choose to make it explicit by using map2.toSeq,
groups elements based on their first tuple part (group part of groupMapReduce),
maps grouped values to their second tuple part (map part of groupMapReduce),
reduces mapped values (_+_) by summing them (reduce part of groupMapReduce).
Andrzej Doyle's answer contains a great explanation of semigroups which allows you to use the |+| operator to join two maps and sum the values for matching keys.
There are many ways something can be defined to be an instance of a typeclass, and unlike the OP you might not want to sum your keys specifically. Or, you might want to do operate on a union rather than an intersection. Scalaz also adds extra functions to Map for this purpose:
https://oss.sonatype.org/service/local/repositories/snapshots/archive/org/scalaz/scalaz_2.11/7.3.0-SNAPSHOT/scalaz_2.11-7.3.0-SNAPSHOT-javadoc.jar/!/index.html#scalaz.std.MapFunctions
You can do
import scalaz.Scalaz._
map1 |+| map2 // As per other answers
map1.intersectWith(map2)(_ + _) // Do things other than sum the values
The fastest and simplest way:
val m1 = Map(1 -> 1.0, 3 -> 3.0, 5 -> 5.2)
val m2 = Map(0 -> 10.0, 3 -> 3.0)
val merged = (m2 foldLeft m1) (
(acc, v) => acc + (v._1 -> (v._2 + acc.getOrElse(v._1, 0.0)))
)
By this way, each of element's immediately added to map.
The second ++ way is:
map1 ++ map2.map { case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Unlike the first way, In a second way for each element in a second map a new List will be created and concatenated to the previous map.
The case expression implicitly creates a new List using unapply method.
Here's what I ended up using:
(a.toSeq ++ b.toSeq).groupBy(_._1).mapValues(_.map(_._2).sum)
This is what I came up with...
def mergeMap(m1: Map[Char, Int], m2: Map[Char, Int]): Map[Char, Int] = {
var map : Map[Char, Int] = Map[Char, Int]() ++ m1
for(p <- m2) {
map = map + (p._1 -> (p._2 + map.getOrElse(p._1,0)))
}
map
}
Using the typeclass pattern, we can merge any Numeric type:
object MapSyntax {
implicit class MapOps[A, B](a: Map[A, B]) {
def plus(b: Map[A, B])(implicit num: Numeric[B]): Map[A, B] = {
b ++ a.map { case (key, value) => key -> num.plus(value, b.getOrElse(key, num.zero)) }
}
}
}
Usage:
import MapSyntax.MapOps
map1 plus map2
Merging a sequence of maps:
maps.reduce(_ plus _)
I've got a small function to do the job, it's in my small library for some frequently used functionality which isn't in standard lib.
It should work for all types of maps, mutable and immutable, not only HashMaps
Here is the usage
scala> import com.daodecode.scalax.collection.extensions._
scala> val merged = Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _)
merged: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4)
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
And here's the body
def mergedWith(another: Map[K, V])(f: (V, V) => V): Repr =
if (another.isEmpty) mapLike.asInstanceOf[Repr]
else {
val mapBuilder = new mutable.MapBuilder[K, V, Repr](mapLike.asInstanceOf[Repr])
another.foreach { case (k, v) =>
mapLike.get(k) match {
case Some(ev) => mapBuilder += k -> f(ev, v)
case _ => mapBuilder += k -> v
}
}
mapBuilder.result()
}
https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190
For anyone coming across an AnyVal error, convert the values as follows.
Error:
"could not find implicit value for parameter num: Numeric[AnyVal]"
(m1.toSeq ++ m2.toSeq).groupBy(_._1).mapValues(_.map(_._2.asInstanceOf[Number].intValue()).sum)
What is the best way to create a Map[K,V] from a Set[K] and function from K to V?
For example, suppose I have
scala> val s = Set(2, 3, 5)
s: scala.collection.immutable.Set[Int] = Set(2, 3, 5)
and
scala> def func(i: Int) = "" + i + i
func: (i: Int)java.lang.String
What is the easiest way of creating a Map[Int, String](2 -> "22", 3 -> "33", 5 -> "55")
You can use foldLeft:
val func2 = (r: Map[Int,String], i: Int) => r + (i -> func(i))
s.foldLeft(Map.empty[Int,String])(func2)
This will perform better than Jesper's solution, because foldLeft constructs the Map in one pass. Jesper's code creates an intermediate data structure first, which then needs to be converted to the final Map.
Update: I wrote a micro benchmark testing the speed of each of the answers:
Jesper (original): 35s 738ms
Jesper (improved): 11s 618ms
dbyrne: 11s 906ms
Rex Kerr: 12s 206ms
Eastsun: 11s 988ms
Looks like they are all pretty much the same as long as you avoid constructing an intermediate data structure.
What about this:
(s map { i => i -> func(i) }).toMap
This maps the elements of s to tuples (i, func(i)) and then converts the resulting collection to a Map.
Note: i -> func(i) is the same as (i, func(i)).
dbyrne suggests creating a view of the set first (see his answer and comments), which prevents an intermediate collection from being made, improving performance:
(s.view map { i => i -> func(i) }).toMap
scala> import collection.breakOut
import collection.breakOut
scala> val set = Set(2,3,5)
set: scala.collection.immutable.Set[Int] = Set(2, 3, 5)
scala> def func(i: Int) = ""+i+i
func: (i: Int)java.lang.String
scala> val map: Map[Int,String] = set.map(i => i -> func(i))(breakOut)
map: Map[Int,String] = Map(2 -> 22, 3 -> 33, 5 -> 55)
scala>
In addition to the existing answers,
Map() ++ set.view.map(i => i -> f(i))
is pretty short and performs as well as the faster answers (fold/breakOut).
(Note the view to prevent creation of a new collection; it does the remapping as it goes.)
The other solutions lack creativity. Here's my own version, though I'd really like to get rid of the _.head map.
s groupBy identity mapValues (_.head) mapValues func
As with all great languages, there's a million ways to do everything.
Here's a strategy that zips the set with itself.
val s = Set(1,2,3,4,5)
Map(s.zip(s.map(_.toString)).toArray : _*)
EDIT: (_.toString) could be replaced with some function that returns something of type V
Without definition of func(i: Int) using "string repeating" operator *:
scala> s map { x => x -> x.toString*2 } toMap
res2: scala.collection.immutable.Map[Int,String] = Map(2 -> 22, 3 -> 33, 5 -> 55)