Reputation: 1626
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
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
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)
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
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
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
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