tekumara
tekumara

Reputation: 8807

Scala - flatten Seq containing Seq

Given:

Seq(1,2,3) map {
  case 1 => 11
  case 2 => Seq(12,13,14)
  case 3 => 15
}

How can I elegantly flatten this to a Seq[Int] containing Seq(11,12,13,14,15) ?

Upvotes: 1

Views: 7190

Answers (4)

Dmitry Ginzburg
Dmitry Ginzburg

Reputation: 7461

If you already have this list, you may do flatMap to achieve the required result:

val list = Seq(11, Seq(12, 13, 14), 15)

val flattened = list.flatMap {
    case x: Int => Seq(x)
    case y: Seq[Int] => y
}

To avoid warnings (although, it'll not help you in making this code type-safe) you may use following:

val flattened = list.flatMap {
    case x: Int => Seq(x)
    case y: Seq[_] => y.asInstanceOf[Seq[Int]]
}

Upvotes: 0

Martijn
Martijn

Reputation: 12091

When you have Seq(1,2,3) you have a Seq[Int].

after your map operation you have a problem though

val mapped = Seq(1,2,3) map {
  case 1 => 11
  case 2 => Seq(12,13,14)
  case 3 => 15
}

What is the type of mapped? A reasonable answer to this could be that it doesn't have a type. That's close to the truth. The resulting type is Seq[Any]. Unfortunately, this is a completely useless type. It's not something you could do anything useful with in a typesafe way, and a good point could be made that Scala shouldn't have allowed this type to be inferred in the first place.

The solution is not to let it get that far in the first place, but map to something that does have a sensible type. The solution shown by Simon is a reasonable approach:

val mapped = Seq(1,2,3) map {
  case 1 => Seq(11)
  case 2 => Seq(12,13,14)
  case 3 => Seq(15)
  case _ => throw new Exception("uh oh, didn't account for this to happen!")
}

Now mapped is a Seq[Seq[Int]], and we can do more useful things with it, for example flatten it to a Seq[Int] with mapped.flatten

But we can get there in one go. There is an operation called flatMap on Seq[A] that takes a function A => Seq[A] as it's argument, and returns a single Seq[A].

val flatmapped = Seq(1,2,3) flatMap {
  case 1 => Seq(11)
  case 2 => Seq(12,13,14)
  case 3 => Seq(15)
  case _ => throw new Exception("uh oh, didn't account for this to happen!")
}

flatmapped is now Seq(11, 12, 13, 14, 15)


Aside

It turns out, by the way, that it's very useful to have this operation on all sorts of parameterized types: (F[A], A => F[A]) => F[A]

For example:

Option[A].flatMap(a: A => Option[A]): Option[A]

def squareroot(x: Double): Option[Double] = if (x >= 0) Some(Math.sqrt(x))
                                            else None

Some(4.0).flatMap(squareroot) == Some(2.0)
Some(-1.0).flatMap(squareroot) == None
None.flatMap(squareroot) == None

or

Future[A].flatMap(a: A => Future[A]): Future[A]

This operation is sometimes called flatMap, and sometimes called bind, and sometimes represented as =>>, and if a type (call it F[A]) that support this operation, and support another operation that can create an F[A] from an A as well (sometimes called point and sometimes called return), and follow some conditions on how these operations compose, F[A] forms a Monad

Upvotes: 1

Odomontois
Odomontois

Reputation: 16308

Here's another way to do it:

implicit def unitSeq[T](x: T): Seq[T] = Seq(x)

Seq(1, 2, 3) flatMap {
  case 1 => 11
  case 2 => Seq(12, 13, 14)
  case 3 => 15
}

res0: Seq[Int] = List(11, 12, 13, 14, 15)

Upvotes: 1

Simon
Simon

Reputation: 6353

Here's one way to do it:

Seq(1,2,3) flatMap {
  case 1 => Seq(11)
  case 2 => Seq(12,13,14)
  case 3 => Seq(15)
}

res0: Seq[Int] = List(11, 12, 13, 14, 15)

Upvotes: 7

Related Questions