【问题标题】:How do I find the min() or max() of two Option[Int]如何找到两个 Option[Int] 的 min() 或 max()
【发布时间】:2012-09-28 12:22:08
【问题描述】:

您如何在下面找到minValue? 我有自己的解决方案,但想看看其他人会怎么做。

val i1: Option[Int] = ...
val i2: Option[Int] = ...
val defaultValue: Int = ...
val minValue = ?

【问题讨论】:

    标签: scala max option min


    【解决方案1】:

    更新:我刚刚注意到我下面的解决方案和您的答案中的解决方案表现不同 - 我将您的问题读作要求两个值中的最小值当有两个值时,但在您的回答你有效地对待None,就好像它包含一个比其他任何东西都大(对于min)或小(对于max)的值。

    更具体地说:如果i1Some(1) 并且i2None,我的解决方案将返回默认值,而您的解决方案将返回1。

    如果您想要后一种行为,您可以为Option[A] 使用默认的半群实例,为Int 使用tropical semigroup。例如,在 Scalaz 7 中,你会这样写:

    import scalaz._, Scalaz._
    
    optionMonoid(Semigroup.minSemigroup[Int]).append(i1, i2) getOrElse defaultValue
    

    或以下简写:

    Tags.Min(i1) |+| Tags.Min(i2) getOrElse defaultValue
    

    它不像下面的应用函子解决方案那样干净,但如果这是你的问题,那就是你的问题。


    这是一种不涉及创建额外列表的更惯用的方式:

    (for { x <- i1; y <- i2 } yield math.min(x, y)) getOrElse defaultValue
    

    或者,等效地:

    i1.flatMap(x => i2.map(math.min(x, _))) getOrElse defaultValue
    

    您正在做的是将两位函数 (min) “提升”为应用函子 (Option)。 Scalaz 使用其适用的构建器语法使这变得简单:

    import scalaz._, Scalaz._
    
    (i1 |@| i2)(math.min) getOrElse defaultValue
    

    在这种情况下,标准库解决方案并没有那么优雅,但这是一个有用的抽象知识。

    【讨论】:

    • 感谢您的回答,特拉维斯。它的简洁令人愉悦,尽管有两件事让我无法使用它:它使用第三方库,为了理解它,我需要阅读一篇关于“数学和代数几何中相对较新的领域”的文章。
    • 为此我添加了一些更实际的方法,使用 cats
    【解决方案2】:

    我使用以下方法解决了类似的问题。我们处理两个选项都有值时的特殊情况,否则我们使用 API 方法Option.orElse

    val a: Option[Int]  = Some(10)
    val b: Option[Int] = Some(20)
    val c: Option[Int] = (a, b) match {
      case (Some(x), Some(y)) => Some(x min y)
      case (x, y) => x orElse y
    }
    

    【讨论】:

      【解决方案3】:

      我想这就是你所追求的:

      val minValue = List(i1, i2).flatten match {
        case Nil => defaultValue
        case xs => xs.min
      }
      

      我会避免使用sorted,因为排序需要比简单地找到最大值或最小值更多的处理(尽管在这种情况下它可能没有太大区别)。

      【讨论】:

      • 我认为这可能是最好的方法,尽管不幸的是惯用的风格使它长了四行。我试图缩短它(通过删除匹配)并提出((xs:List[Int]) =&gt; {if (xs == Nil) defaultValue else xs.min})(List(i1, i2).flatten),但为了简洁起见,这显然是一种混淆。
      • 为什么不List(Option(defaultValue), i1, i2).flatten.min
      • flatten.min 会抛出 UnsupportedOperationException with empty.min 如果 List 为空,当你展平时,如果列表中的所有元素都为 None,你最终会得到一个空列表
      【解决方案4】:
      val minValue: Int = List(i1, i2).flatten.sorted.headOption getOrElse defaultValue
      

      【讨论】:

        【解决方案5】:

        你可以在 for 表达式中使用模式,不匹配模式的值将被丢弃。

        (for (Some(x) <- List(None, Some(3))) yield x) max
        

        虽然不如 List.flatten 方法。

        【讨论】:

          【解决方案6】:

          另一个未提及的选项是使用reduceLeftOption(根据需要互换math.maxmath.min):

          val min = (first ++ second).reduceLeftOption(math.min).getOrElse(defaultValue)
          

          scala> val first = Some(10)
          first: Some[Int] = Some(10)
          
          scala> val second: Option[Int] = None
          second: Option[Int] = None
          
          scala> val defaultMin = -1
          defaultMin: Int = -1
          
          scala> (first ++ second).reduceLeftOption(math.min).getOrElse(defaultMin)
          res7: Int = 10
          
          scala> val first: Option[Int] = None
          first: Option[Int] = None
          
          scala> (first ++ second).reduceLeftOption(math.min).getOrElse(defaultMin)
          res8: Int = -1
          
          scala> val first = Some(10)
          first: Some[Int] = Some(10)
          
          scala> val second = Some(42)
          second: Some[Int] = Some(42)
          
          scala> (first ++ second).reduceLeftOption(math.min).getOrElse(defaultMin)
          res9: Int = 10
          

          【讨论】:

            【解决方案7】:

            我们可以将 2 个Options 组合成一个IterableOption++ 运算符,这样我们就可以使用minOption(以很好地处理由@ 形成的空迭代器的情况987654328@ case) 并在必要时使用getOrElse 回退到默认值:

            (optionA ++ optionB).minOption.getOrElse(-1)
            // None and None       => -1
            // Some(5) and None    => 5
            // None and Some(5)    => 5
            // Some(5) and Some(3) => 3
            

            【讨论】:

              【解决方案8】:

              如果您想避免使用 scalaz 和 map/for/getOrElse,您可以执行以下操作:

              val minValue = (i1, i2) match {
                case (Some(x), Some(y)) => math.min(x, y)
                case _ => defaultValue
              }
              

              【讨论】:

              • 感谢您的贡献,但如果定义了任何一个,我想要最小值,而不仅仅是定义了两个时。
              【解决方案9】:

              tl;博士

              您可以使用自定义 cats Semigroup 实例来完成您需要优雅的操作:

              import cats.kernel.Semigroup
              import cats.instances.option._ // this import is for cats std option combiner
              import cats.syntax.semigroup._
              
              object Implicits {
               implicit val intMinSemigroup: Semigroup[Int] =
                 (x: Int, y: Int) => math.min(x, y)
              
               implicit val intMaxSemigroup: Semigroup[Int] =
                 (x: Int, y: Int) => math.max(x, y)
              }
               
              import Implicits.intMinSemigroup
              // these are results for minSemigroup
              // List((Some(1),Some(1),Some(2)), (Some(1),Some(1),None), (None,Some(2),Some(2)), (None,None,None))
              //import Implicits.intMaxSemigroup
              // these are results for maxSemigroup
              // List((Some(1),Some(2),Some(2)), (Some(1),Some(1),None), (None,Some(2),Some(2)), (None,None,None))
                 
              for {
               maybeA <- Seq(Some(1), None)
               maybeB <- Seq(Some(2), None)
              } yield (maybeA, maybeA |+| maybeB, maybeB)
              

              如果你想用默认值替换None,你可以使用两次组合:

              val defaultValue: Int = 3
              val optionMin = for {
                maybeA <- Seq(Some(1), None)
                maybeB <- Seq(Some(2), None)
              } yield (maybeA |+| maybeB) |+| Some(defaultValue)
              // List(Some(1), Some(1), Some(2), Some(3))
              

              工作原理

              简而言之,Semigroup[A]typeclass,用于组合两个相同类型的值 A 到一个类型为 A 的值。 这里我们使用标准猫OptionMonoid(它扩展了Semigroup[Option[A]])这里的源码:

              class OptionMonoid[A](implicit A: Semigroup[A]) extends Monoid[Option[A]] {
                def empty: Option[A] = None
                def combine(x: Option[A], y: Option[A]): Option[A] =
                  x match {
                    case None => y
                    case Some(a) =>
                      y match {
                        case None    => x
                        case Some(b) => Some(A.combine(a, b))
                      }
                  }
              }
              

              我们看到它需要他自己进行选项匹配,我们应该给他工作的所有东西都是implicit A: Semigroup[A]。在我们的例子中,我们为minmax 编写了两个不同的组合器:

              object Implicits {
               implicit val intMinSemigroup: Semigroup[Int] =
                 (x: Int, y: Int) => math.min(x, y)
              
               implicit val intMaxSemigroup: Semigroup[Int] =
                 (x: Int, y: Int) => math.max(x, y)
              }
              

              因此,我们导入组合器(即import Implicits.intMinSemigroup)并仅使用cats.syntax.semigroup 来使用combine 函数作为运算符|+|

              maybeA |+| maybeB.

              总之,您可以为任何类型(不仅是Int)定义自定义semigroup,并combine这种类型的选项在导入一些 cats syntaxinstances 之后。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-08-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-05-23
                • 2023-04-10
                相关资源
                最近更新 更多