How do I easily convert from one collection type to another during a filter, map, flatMap in Scala?
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)
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, 2022Comments
-
Jean-Philippe Pellet almost 2 years
Suppose I have a
List[Int]
, and I want to calltoString
on each element, and get back the result as aVector[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 aList
, but I'd like theString
argument to be inferred from the filter function.Or should I explicitly pass a
CanBuildFrom
instance? Where do I get these from — forSeq
s,Set
s andMap
s? -
Jean-Philippe Pellet about 13 yearsThanks, using
breakOut
seems to work well formap
andflatMap
, but not withfilter
, which does not accept aCanBuildFrom
. I can use thetoIndexedSeq
forVector
s, but what if I want an arbitrary collection, for which aCanBuildFrom
is available? Do I have to go through natural transformations? -
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
forIndexedSeq
) not be good enough? -
Jean-Philippe Pellet about 13 yearsI 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 about 13 years@JPP
filter
cannot return a different collection. -
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 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 over 5 yearsJust as a precaution... if the original collection is a
Set
, you will probably want to callto
beforemap
. Otherwise, the intermediate collection will fillter out duplicates. Consider:Set("Bob", "Don", "Susan").map(_.length).to[Vector]
yieldsVector(3, 5)
instead ofVector(3, 3, 5)
. You probably wantSet("Bob", "Don", "Susan").to[Vector].map(_.length)
instead.