David Moles
David Moles

Reputation: 51113

Scala: filter and cast in one operation

Given a heterogeneous sequence, how can I pull out only members of a certain type, and operate on those members in a type-safe way?

If I have:

trait Foo
case class Bar(baz: String)
case class Qux(bar: Bar, quux: String) extends Foo
case class Corge(grault: Int) extends Foo

If I want to take this sequence of mixed Corges and Quxes...

val s = Seq[Foo](Corge(1), Qux(Bar("2"), "3"), Qux(Bar("4"), "5"), Corge(6), Qux(Bar("2"), "7"))

...and pull out just the Quxes, grouped by Bar:

Map(
  Bar(2) -> List(Qux(Bar(2),3), Qux(Bar(2),7)), 
  Bar(4) -> List(Qux(Bar(4),5))
)

I can do this:

s filter { f => f.isInstanceOf[Qux] } groupBy { 
    f => f.asInstanceOf[Qux].bar }

Or I can do this:

(s.filter({ f => f.isInstanceOf[Qux] }).asInstanceOf[Seq[Qux]]) groupBy { 
    q => q.bar }

But either way I need two instanceOf checks, when it seems like I should be able to get away with one. Or none. Is there some clever pattern-matching solution I'm missing?

Upvotes: 3

Views: 762

Answers (2)

Randall Schulz
Randall Schulz

Reputation: 26486

Even better than Daniel Martin's solution is to use collect:

val a: Seq[Any] = List(1, 2, "asdf", 4)
a: Seq[Any] = List(1, 2, asdf, 4)

val b = a collect { case s: String => s }
b: Seq[String] = List(asdf)

Upvotes: 14

Daniel Martin
Daniel Martin

Reputation: 23548

No explicit instanceOf needed, just the implicit kind you get with using a class in a case:

$ scala
Welcome to Scala version 2.9.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val a : Seq[Any] = List(1, 2, "asdf", 4)
a: Seq[Any] = List(1, 2, asdf, 4)

scala> val b = a flatMap {i:Any => i match { case s:String => Some(s); case _ => None }}
b: Seq[String] = List(asdf)

Note the types of a and b.

Upvotes: 1

Related Questions