【问题标题】:Handling two Options in Scala在 Scala 中处理两个选项
【发布时间】:2020-03-12 07:38:39
【问题描述】:

我有 2 个选项,我需要取它们持有的值的平均值。

可能缺少一个或两个。如果缺少其中一个值,我只会将另一个值作为平均值。但如果两者都缺失,我会使用一些默认值。

如何以干净的方式做到这一点?

我可以使用 isEmpty 来检查值的缺失,但这不就和空值检查一样吗?

【问题讨论】:

    标签: scala scala-option


    【解决方案1】:

    我想这是不言自明的:

    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").
    【解决方案2】:

    您可以将选项视为 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
    • @jwvh 是的,我想过这个问题,但想明确说明...
    【解决方案3】:
    val v = List(opt1, opt2).flatten
    
    if (v.nonEmpty) {
      v.sum / v.size
    } else {
      <default value>
    }
    

    这可以扩展为使用任意数量的可选值。

    【讨论】:

      【解决方案4】:

      另一种可能性:

      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,因此您可以使用orElseleftright 定义为备用值。最后,使用getOrElse 检索结果,如果两个输入都为空,则返回default

      您可以对其进行调整以采用任何行为。要创建一个在两个选项都为空时抛出的函数,您可以执行以下操作:

      val assertAvg = avg(_ : Option[Double], _ : Option[Double])(sys.error("both operands are empty"))
      

      这是因为throw 表达式的类型是Nothing,它是任何其他类型(包括Double)的子类型,即它可以作为任何表达式的结果返回,而不管预期的类型。

      代码(和一些测试)可用here on Scastie

      【讨论】:

        【解决方案5】:

        在我看来,您应该在选项中保持平均,然后再使用默认值。

        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
        

        【讨论】:

          猜你喜欢
          • 2014-12-21
          • 2014-02-18
          • 1970-01-01
          • 1970-01-01
          • 2013-12-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-14
          相关资源
          最近更新 更多