任何人都可以帮助这个Scala新手吗?以前,我们在具有以下数量的实体列表中汇总了一些数量:
sum = entities.foldLeft(0.0)(_ + _.quantity)
现在数量是Option[Double]
,总和也是如此。如何使用惯用的Scala转换它?
如果任何实体的数量为None
,则总和也应为None
。
否则总和应为Some(total)
。
编辑:将此内容放入单元测试中,以便我可以尝试所有答案。请注意,如果任何数量为无,我确实需要结果为无,因为缺少数量意味着我们还没有完成,所以总数应该反映这一点。即使你没有得到正确的答案,如果你帮助引导我或其他人,或者帮助我学习新的东西,我还是会投票。
编辑:@ sepp2k赢得工作解决方案和解释。感谢大家的学习!
答案 0 :(得分:9)
您可以使用Option
的{{1}}和flatMap
方法合并两个map
s,如果两个Option
结果为Some(f(x,y))
{ {1}} {s} Option
和Some(x)
或Some(y)
。
None
根据您的评论进行修改:
以下是一个示例用法:
entities.foldLeft(Some(0.0):Option[Double]) {
(acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
是的,如果任何实体是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)
,它将返回None
。
关于None
和map
:
flatMap
接受map
类型的f
函数,并返回A => B
的{{1}}和Some(f(x))
的{{1}}。< / p>
Some(x)
,其中None
是None
类型的函数,xo.flatMap(f)
是f
,返回A => Option[B]
iff {{1} } xo
而Option[A]
是Some(y)
。在所有其他情况下(例如xo
为Some(x)
或f(x)
为Some(y)
),它会返回xo
。
因此,None
表达式返回f(x)
iff None
为None
且acco.flatMap(acc => x.quantity.map(_ + acc))
为y + acc
。如果x.quantity
和Some(y)
中的一个或两个都是acco
,则结果将为无。由于这是在折叠中,这意味着对于下一次迭代,Some(acc)
的值也将是x.quantity
,因此最终结果将是acco
。
答案 1 :(得分:4)
我喜欢在使用for
时使用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
这对Java人员来说应该很容易遵循..
(很抱歉Entity
的实施,我很难想出一个quantity()
实现,实际上在某些时候返回了None。)
编辑:添加了解释
你想要的是计算总和,对吧?使用此解决方案,如果quantity()
为列表中的所有实体返回None
,则总和将为0.为什么?因为quantities
集合不包含任何元素。
将Option
与for
一起使用时,您可以非常好的方式从结果列表中删除所有None
个元素。这是一行:
q <- entity.quantity
..实际上会从结果列表中删除所有None
结果,并从Double
中提取Some(x)
。所以:
yield q
..只返回Double
种类型。这使您有机会在结果集合上使用sum()函数,因为集合包含Double
而不是Option[Double]
。 sum
操作非常易读!
答案 2 :(得分:3)
“惯用语”非常贴切,因为这被称为“idiomatic function application”,即将一个功能“提升”为“成语”(更现代的:“应用程序”)。
在Scalaz中,可以按照以下方式完成:
import scalaz._
import Scalaz._
val add: (Int, Int) => Int = (x, y) => x + y
val addOptions: (Option[Int], Option[Int]) => Option[Int] = add.lift
或者像这样:
List(1,2,3).map(some(_)).foldLeft(some(0))(add.lift)
答案 3 :(得分:2)
好吧,我认为这可以满足您的需求。 (以前的答案误读了你的要求)。
entities.find(_.quantity == None) match {
case Some(_) => None
case None => Some(entities.map(_.quantity).flatten.reduceLeft(_ + _))
}
我认为另一个答案更“惯用”,但在我看来这更容易理解。
答案 4 :(得分:2)
编辑:因为实体也是一个可选的值代码适用于那个
虽然@ sepp2k的答案是正确的,如果您的Option[Entity]
字段Double
quantity
,您需要的内容应该是以下内容:
entities.foldLeft(Option(0d)) {
(sum, oe) => for {
s <- sum
e <- oe
q <- e.quantity
} yield s + q
}
封闭内的for
- 理解与@ sepp2k的答案中的flatMap
/ map
相同,但根据我的经验,初学者更容易理解。
答案 5 :(得分:2)
许多现有的解决方案都有效(并且接受的解决方案是规范的,并且是我通常会使用的解决方案),但是如果点击None
很常见,那么效率会更高。当它达到第一个None
时,它会使评估短路。请注意,这是尾递归的。
// Replace Option[Double] by your entity type, and it.next with it.next.quantity
def total(it: Iterator[Option[Double]], zero: Double = 0.0): Option[Double] = {
if (it.hasNext) {
it.next match {
case Some(x) => total(it,zero+x)
case None => None
}
}
else Some(zero)
}
// To use: total(entities.iterator)
答案 6 :(得分:2)
让我们不要忘记,更大的兄弟 fold right 一旦遇到无,就可以退出递归。一切都以非常优雅的方式:
def sumOptsFoldRight = (entities:List[Option[Double]]) =>
entities.foldRight(Some(0.0):Option[Double])((accOpt,xOpt) => xOpt match {
case None => None
case Some(xVal) => accOpt.map(xVal + _)
})
答案 7 :(得分:1)
我会明确检查是否缺少使用
entities.forall(_.quantity.isDefined)
例如:
scala> case class Entity(quantity: Option[Double])
defined class Entity
scala> val entities = List(Entity(Some(10.0)), Entity(None), Entity(Some(15.0)))
entities: List[Entity] = List(Entity(Some(10.0)), Entity(None), Entity(Some(15.0)))
scala> if (entities.forall(_.quantity.isDefined)) {
| Some(entities.flatMap(_.quantity).reduceLeft(_+_))
| } else None
res6: Option[Double] = None
答案 8 :(得分:1)
val sum = entities.foldLeft(Some(0.0):Option[Double]){
(s,e) => if (s.isEmpty || e.quantity.isEmpty) None else Some(s.sum + e.quantity.sum)}
或
val sum = if(entities.exists(_.quantity.isEmpty)) None
else Some(entities.flatMap(_.quantity).sum)
答案 9 :(得分:1)
这与sepp2k / Moritz的答案相同,但分为两个功能,以使事情更清晰。
def addOptionDouble(optionalA: Option[Double], optionalB: Option[Double]): Option[Double] =
for {
a <- optionalA
b <- optionalB
} yield a + b
def sumQuantitiesOfEntities(entities: Traversable[Entity]): Option[Double] =
entities.foldLeft(Option(0.0)) {
(acc, entity) => addOptionDouble(acc, entity.quantity)
}