如何将此foldLeft:Double表达式转换为使用Option [Double]?

时间:2010-09-01 09:38:16

标签: scala functional-programming

任何人都可以帮助这个Scala新手吗?以前,我们在具有以下数量的实体列表中汇总了一些数量:

sum = entities.foldLeft(0.0)(_ + _.quantity)

现在数量是Option[Double],总和也是如此。如何使用惯用的Scala转换它?

如果任何实体的数量为None,则总和也应为None。 否则总和应为Some(total)

编辑:将此内容放入单元测试中,以便我可以尝试所有答案。请注意,如果任何数量为无,我确实需要结果为无,因为缺少数量意味着我们还没有完成,所以总数应该反映这一点。即使你没有得到正确的答案,如果你帮助引导我或其他人,或者帮助我学习新的东西,我还是会投票。

编辑:@ sepp2k赢得工作解决方案和解释。感谢大家的学习!

10 个答案:

答案 0 :(得分:9)

您可以使用Option的{​​{1}}和flatMap方法合并两个map s,如果两个Option结果为Some(f(x,y)) { {1}} {s} OptionSome(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

关于Nonemap

flatMap接受map类型的f函数,并返回A => B的{​​{1}}和Some(f(x))的{​​{1}}。< / p>

Some(x),其中NoneNone类型的函数,xo.flatMap(f)f,返回A => Option[B] iff {{1} } xoOption[A]Some(y)。在所有其他情况下(例如xoSome(x)f(x)Some(y)),它会返回xo

因此,None表达式返回f(x) iff NoneNoneacco.flatMap(acc => x.quantity.map(_ + acc))y + acc。如果x.quantitySome(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集合不包含任何元素。

Optionfor一起使用时,您可以非常好的方式从结果列表中删除所有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)
  }