Can anyone help this Scala newbie? Previously, we summed a number of quantities in a list of entities with those quantities with:
sum = entities.foldLeft(0.0)(_ + _.quantity)
Now the quantity is an Option[Double]
, and so is the sum. How can I convert this using idiomatic Scala?
If any entity's quantity is None
then the sum should also be None
.
Otherwise the sum should be Some(total)
.
Edit: Putting this thing into a unit test so that I can try all your answers out. Please note that I do need the result to be None if any quantity is None, because missing quantities mean we haven't finished yet, so the total should reflect this. Even if you don't get the right answer, if you help lead me or others to it, or help me learn something new, I'll upvote.
Edit: @sepp2k wins for a working solution plus explanation. Thanks to all for the learning!
You can use Option
's flatMap
and map
methods to combine two Option
s, so that the result will be Some(f(x,y))
if the two Option
s are Some(x)
and Some(y)
or None
otherwise.
entities.foldLeft(Some(0.0):Option[Double]) {
(acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
Edit in response to your comments:
Here's an example usage:
scala> case class Foo(quantity:Option[Double]) {}
defined class Foo
scala> val entities: List[Foo] = List(Foo(Some(2.0)), Foo(Some(1.0)), Foo(None))
scala> entities.foldLeft(Some(0.0):Option[Double]) {
(acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
res0: Option[Double] = None
scala> val entities: List[Foo] = List(Foo(Some(2.0)), Foo(Some(1.0)))
scala> entities.foldLeft(Some(0.0):Option[Double]) {
(acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
res1: Option[Double] = Some(3.0)
So yes, it will return None
if any of the entities are None
.
Regarding map
and flatMap
:
map
takes a function f
of type A => B
and returns Some(f(x))
for Some(x)
and None
for None
.
xo.flatMap(f)
, where f
is a function of type A => Option[B]
and xo
is an Option[A]
, returns Some(y)
iff xo
is Some(x)
and f(x)
is Some(y)
. In all other cases (i.e. if xo
is None
or f(x)
is None
) it returns None
.
So the expression acco.flatMap(acc => x.quantity.map(_ + acc))
returns y + acc
iff x.quantity
is Some(y)
and acco
is Some(acc)
. If one or both of x.quantity
and acco
are None
, the result will be none. Since this is inside a fold that means that for the next iteration the value of acco
will also be None
and thus the end result will be None
.
I like to use for
when working with Option
:
// ========= Setup ===============
case class Entity(x: Double){
// Dummy
def quantity = if (x < 2) None
else Some(x)
}
val entities = List(Entity(1), Entity(5), Entity(7))
// ========= Calculate ===============
val quantities = for{
entity <- entities
q <- entity.quantity
} yield q
val qSum = quantities.sum
This should be easy for Java people to follow..
(Sorry for the implementation of Entity
, I had a hard time to come up with a quantity()
implementation that actually returned None at some points.)
EDIT: Added explanation
What you wanted was to calculate the sum, right? With this solution, if quantity()
returns None
for all entities in the list then the sum will be 0. Why? Because the quantities
collection holds no elements.
When using Option
with for
you can remove all None
elements from the resulting list in a very nice way. It is the line:
q <- entity.quantity
..that actually removes all None
results from the resulting list and extract the Double
from the Some(x)
. So:
yield q
.. will only return Double
types. This gives you the opportunity to use the sum() function on the resulting collection, since the collection holds Double
instead of Option[Double]
. The sum
operation is very readable!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With