Ellesedil
Ellesedil

Reputation: 1626

Flattening a map with a value of Option[T]

I have a map that's currently represented in a sequence like Seq[(String, Option[T])]. I don't care about the None values in the map since I want to do some operations like, say, sorting on them so I can identify which key to use. I can do something like this:

val mapping: Seq[(String, Option[Foo])]

mapping.filter(_._2.isDefined)
    .sortBy(_._2.get.someInt)
    .headOption.map(_._1)

...but there has to be a better way to write this. In particular, the get makes me uncomfortable since the value is still of a type Option[Foo], although there should be no None values in it.

Is there a better way?

Upvotes: 1

Views: 887

Answers (5)

Sebastian Celestino
Sebastian Celestino

Reputation: 1428

If you want to remove None elements you can use collect or flatMap instead of filter. And if you want to be more expressive avoid to use _1 or _2 Tuple methods and replace them with a case.

case class Foo(someInt: Int)

val list = Seq("1" -> Option(Foo(1)), "2" -> Option(Foo(7)), "3" -> None, "4" -> Option(Foo(-2)))

val r2 = list.flatMap { case (key, foo) => foo.map( key -> _.someInt) }.sortBy { case (_, value) => value }.headOption.map { case (key, _) => key }

val r = list.collect { case (key, Some(foo)) => (key, foo.someInt) }.sortBy { case (_, value) => value}.headOption.map { case (key, _) => key }


println(r)
println(r2)

Upvotes: 0

Andrey Tyukin
Andrey Tyukin

Reputation: 44992

I assume the following definitions for Foo and mapping:

case class Foo(someInt: Int)
val mapping: Seq[(String, Option[Foo])] = Seq(("a", Some(Foo(42))), ("b", None))

you have several options:

collect

The collect method combines pattern matching with filtering:

mapping.collect{case (s,Some(Foo(i))) => (s,i) }.sortBy(_._2).headOption.map(_._1)

for-yield

Alternatively, you can achieve the same effect with for-yield, it will also throw out all the elements that don't match:

(for ((s, Some(Foo(n))) <- mapping) yield(s, n)).sortBy(_._2).headOption.map(_._1)

O(N) solution with minBy

Also note that sorting the entire collection just to extract the maximum / minimum is unnecessary: there is maxBy / minBy for that:

Option(for ((s, Some(Foo(n))) <- mapping) yield (s, n))
  .filter(_.nonEmpty)
  .map(_.minBy(_._2)._1)

Wrapping the entire sequence into Option is necessary so we can filter out the case when the sequence is empty, so we don't invoke minBy on an empty sequence.

Upvotes: 3

prayagupadhyay
prayagupadhyay

Reputation: 31262

you can use use sortBy on Option[Int] and then clean up unwanted data with collect,

scala> final case class Foo(someInt: Int)
defined class Foo

scala> val data = Seq(("zebra", Some(Foo(26))), 
                      ("banana", Some(Foo(1))), 
                      ("empty", Option.empty[Foo]))
data: Seq[(String, Option[Foo])] = List((zebra,Some(Foo(26))), (banana,Some(Foo(1))), (empty,None))

example:

scala> data.sortBy(_._2.map(_.someInt))
           .collect { case (k, Some(v)) => k}
           .headOption
res1: Option[String] = Some(banana)

Upvotes: 0

Ethan
Ethan

Reputation: 841

If you flatten over a Seq[Option[_]], it will remove all of the None and get the values of the Some. You're right to feel wary about using get, that's generally somewhat unsafe given the (however unlikely) chance of nulls.

val mapping: Seq[(String, Option[Foo])] = ...

mapping.flatmap{case (key, maybeValue) => maybeValue.map((key,_))}
  .sortBy(_._2.someInt)
  .headOption.map(_._1)

Upvotes: 0

Dima
Dima

Reputation: 40508

You can use .collect instead of .filter:

mapping
  .collect { case (s, Some(t)) => s -> t.someInt }
  .sortBy(_._2)
  .headOption
  .map(_._1)

Upvotes: 0

Related Questions