在 Scala 中处理两个选项
Handling two Options in Scala
我有 2 个选项,我需要对它们所持有的值取平均值。
有可能缺少一个或两个。如果缺少一个值,我会取另一个作为平均值。但如果两者都缺失,我会求助于一些默认值。
如何以干净的方式完成此操作?
我可以使用 isEmpty 检查值是否缺失,但这不是与空值检查相同吗?
我想这是不言自明的:
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
是一个密封特征,编译器可以检查并确保模式匹配的所有潜在分支都被覆盖.
您可以将选项视为 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)
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
我有 2 个选项,我需要对它们所持有的值取平均值。
有可能缺少一个或两个。如果缺少一个值,我会取另一个作为平均值。但如果两者都缺失,我会求助于一些默认值。
如何以干净的方式完成此操作?
我可以使用 isEmpty 检查值是否缺失,但这不是与空值检查相同吗?
我想这是不言自明的:
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
是一个密封特征,编译器可以检查并确保模式匹配的所有潜在分支都被覆盖.
您可以将选项视为 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)
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