How do I easily convert from one collection type to another during a filter, map, flatMap in Scala?

19,370

Using breakOut

Use breakOut as the CanBuildFrom and let the typer know what you want your result type to be (unfortunately you need to specify String here)

scala> import collection.breakOut
import collection.breakOut

scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)

scala> res0.map(_.toString)(breakOut) : Vector[String]
res2: Vector[String] = Vector(1, 2, 3)

Using to[Collection] (starting with Scala 2.10.0)

Scala 2.10.0 introduced an easy way to convert a collection to another collection:

scala> List(1, 2, 3).map(_.toString).to[Vector]
res0: Vector[String] = Vector(1, 2, 3)

Using toIndexedSeq

Alternatively ask for an IndexedSeq explicitly:

scala> res0.map(_.toString).toIndexedSeq
res4: scala.collection.immutable.IndexedSeq[String] = Vector(1, 2, 3)

If you want to do this without creating the intermediate List, then:

scala> res0.view.map(_.toString).toIndexedSeq
res5: scala.collection.immutable.IndexedSeq[String] = Vector(1, 2, 3)

Using Natural Transformations

You could do it (awkwardly, but more generally) using natural transformations

scala> trait Trans[F[_], G[_]] {
 | def f2g[A](f : F[A]) : G[A]
 | }
defined trait Trans

Now provide a typeclass instance from the List ~> Vector transformation:

scala> implicit val List2Vector = new Trans[List, collection.immutable.Vector] {
 | def f2g[A](l : List[A]) : Vector[A] = l.map(identity[A])(collection.breakOut)
 | }
List2Vector: java.lang.Object with Trans[List,scala.collection.immutable.Vector] = $anon$1@56329755

Define a wrapper and an implicit conversion:

scala> class Clever[M[_], A](ma : M[A]) { def to[N[_]](implicit t : Trans[M, N]) : N[A] = t.f2g(ma) }
defined class Clever

scala> implicit def ma2clever[M[_], A](ma : M[A]) = new Clever[M, A](ma)
ma2clever: [M[_],A](ma: M[A])Clever[M,A]

Then:

scala> List(1, 2, 3).map(_.toString).to[Vector]
res4: Vector[java.lang.String] = Vector(1, 2, 3)
Share:
19,370
Jean-Philippe Pellet
Author by

Jean-Philippe Pellet

Lecturer (EPFL) & developer (University of Teacher Education, Lausanne, Switzerland). Ph.D. in computer science. Interests include: Language design (big fan of Scala) Machine learning, statistics, statistical causality Pedagogical tools Teaching of computer science and computational thinking You can contact me at jppellet at gmail dot com.

Updated on June 07, 2022

Comments

  • Jean-Philippe Pellet
    Jean-Philippe Pellet almost 2 years

    Suppose I have a List[Int], and I want to call toString on each element, and get back the result as a Vector[String].

    What are the various ways to do this in Scala? Is there a solution with a minimal amount of explicit typing? — i.e., I want to specify that I want a Vector rather than a List, but I'd like the String argument to be inferred from the filter function.

    Or should I explicitly pass a CanBuildFrom instance? Where do I get these from — for Seqs, Sets and Maps?

  • Jean-Philippe Pellet
    Jean-Philippe Pellet about 13 years
    Thanks, using breakOut seems to work well for map and flatMap, but not with filter, which does not accept a CanBuildFrom. I can use the toIndexedSeq for Vectors, but what if I want an arbitrary collection, for which a CanBuildFrom is available? Do I have to go through natural transformations?
  • oxbow_lakes
    oxbow_lakes about 13 years
    @JPP - depends on your use-case, I suspect. Personally I wouldn't use natural transformations - seems like a lot of pain for not much gain. Also, why would the standard form of a collection-type (e.g. Vector for IndexedSeq) not be good enough?
  • Jean-Philippe Pellet
    Jean-Philippe Pellet about 13 years
    I agree, the standard form is usually good enough, and together with views, we avoid the creation of an intermediary representation. I was just curious as to if there was a way to do it. I'll leave the question open for a while and then accept your answer if nothing else presents itself.
  • Daniel C. Sobral
    Daniel C. Sobral about 13 years
    @JPP filter cannot return a different collection.
  • Jean-Philippe Pellet
    Jean-Philippe Pellet over 11 years
    @DanielC.Sobral or oxbow_lakes, Is there a better way to do this in 2.10? Would it be worth updating the answer?
  • Daniel C. Sobral
    Daniel C. Sobral over 11 years
    @Jean-PhilippePellet There's something, indeed. I added a new section, right below the breakOut, which is the most efficient one in terms of performance.
  • Travis Parks
    Travis Parks over 5 years
    Just as a precaution... if the original collection is a Set, you will probably want to call to before map. Otherwise, the intermediate collection will fillter out duplicates. Consider: Set("Bob", "Don", "Susan").map(_.length).to[Vector] yields Vector(3, 5) instead of Vector(3, 3, 5). You probably want Set("Bob", "Don", "Susan").to[Vector].map(_.length) instead.