【发布时间】:2020-03-12 07:38:39
【问题描述】:
我有 2 个选项,我需要取它们持有的值的平均值。
可能缺少一个或两个。如果缺少其中一个值,我只会将另一个值作为平均值。但如果两者都缺失,我会使用一些默认值。
如何以干净的方式做到这一点?
我可以使用 isEmpty 来检查值的缺失,但这不就和空值检查一样吗?
【问题讨论】:
标签: scala scala-option
我有 2 个选项,我需要取它们持有的值的平均值。
可能缺少一个或两个。如果缺少其中一个值,我只会将另一个值作为平均值。但如果两者都缺失,我会使用一些默认值。
如何以干净的方式做到这一点?
我可以使用 isEmpty 来检查值的缺失,但这不就和空值检查一样吗?
【问题讨论】:
标签: scala scala-option
我想这是不言自明的:
val option1 = Some(12.0)
val option2 = None
val default = 0.0
val average = (option1, option2) match {
case (Some(val1), Some(val2)) => (val1 + val2) / 2
case (None, Some(val2)) => val2
case (Some(val1), None) => val1
case (None, None) => default
}
...但如果不是,基本思想是你构造一个选项元组,然后在元组上进行模式匹配。
这样做的好处是显式捕获所有四种潜在情况 + 得到编译器的支持 - 因为 Option 是一个密封特征,编译器可以检查并确保覆盖模式匹配的所有潜在分支。
【讨论】:
case (None, None) => throw new Exception("your exception goes here").
您可以将选项视为 Seq:
val o: Option[Double]
val p: Option[Double]
val default: Double
val s = o.toSeq ++ p.toSeq
val avg = s.reduceOption(_ + _).getOrElse(default) / 1.max(s.size)
【讨论】:
.toSeq。
val v = List(opt1, opt2).flatten
if (v.nonEmpty) {
v.sum / v.size
} else {
<default value>
}
这可以扩展为使用任意数量的可选值。
【讨论】:
另一种可能性:
def avg(left: Option[Double], right: Option[Double])(default: => Double): Double =
left.flatMap(a => right.map(b => (a + b) / 2))
.orElse(left)
.orElse(right)
.getOrElse(default)
你flatMap在左边选项上:如果它不为空,你选择右边选项和map它的内容并与左边选项的内容平均。如果任一选项为空,则结果为None,因此您可以使用orElse 将left 或right 定义为备用值。最后,使用getOrElse 检索结果,如果两个输入都为空,则返回default。
您可以对其进行调整以采用任何行为。要创建一个在两个选项都为空时抛出的函数,您可以执行以下操作:
val assertAvg = avg(_ : Option[Double], _ : Option[Double])(sys.error("both operands are empty"))
这是因为throw 表达式的类型是Nothing,它是任何其他类型(包括Double)的子类型,即它可以作为任何表达式的结果返回,而不管预期的类型。
代码(和一些测试)可用here on Scastie。
【讨论】:
在我看来,您应该在选项中保持平均,然后再使用默认值。
def avgOpt(of:Option[Double]*) = {
val s = of.flatten
s.reduceOption(_ + _).map(_ / s.size)
}
avgOpt(Some(5), None, None).getOrElse(0) //5
avgOpt(Some(5), Some(3), None).getOrElse(0) //4
avgOpt(None, None).getOrElse(0) //0
【讨论】: