15

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

dbyrne
  • 59,111
  • 13
  • 86
  • 103
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 1
    Can you please clarify whether you are talking about a function or a method? You mention *twice* that you mean a function, but you use a method in your code example. Which is it? – Jörg W Mittag Apr 07 '11 at 15:31
  • 2
    The easiest way is as you did: Map[Int, String](2 -> "22", 3 -> "33", 5 -> "55") – Viktor Klang Apr 07 '11 at 22:15
  • duplicate:http://stackoverflow.com/questions/674639 – Eyal Roth Dec 03 '15 at 16:06
  • Possible duplicate of [Scala best way of turning a Collection into a Map-by-key?](http://stackoverflow.com/questions/674639/scala-best-way-of-turning-a-collection-into-a-map-by-key) – Eyal Roth Dec 03 '15 at 16:07

7 Answers7

21

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.

dbyrne
  • 59,111
  • 13
  • 86
  • 103
  • What if I'd add a `.view` before the `.toMap` in my solution - would that prevent building the intermediate collection? – Jesper Apr 07 '11 at 20:44
  • @Jesper: You would need to add the `.view` before the `map`, like this - `(s.view.map { i => i -> func(i) }).toMap` – dbyrne Apr 07 '11 at 20:57
  • @Jesper: Updated my answer. Shows what a dramatic improvement adding `.view` makes. – dbyrne Apr 07 '11 at 21:14
16

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
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • I wonder, is there a way to supply the func explicitly to toMap (i.e., specifying the implicit argument). – subsub Apr 07 '11 at 15:32
  • @subsub if you change `func` to: `def func(i: Int) = i -> ("" + i + i)` then you could do: `(s.view map func).toMap`. So, you'd let `func` return the tuple. – Jesper Apr 07 '11 at 21:47
7
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>
Eastsun
  • 18,526
  • 6
  • 57
  • 81
6

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

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
2

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
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
1

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

jtb
  • 828
  • 1
  • 7
  • 11
1

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)
Antonin Brettsnajdr
  • 4,073
  • 2
  • 20
  • 14