如何找到两个Option[Int]的min()或max()

问题描述 投票:0回答:10

你会如何找到下面的

minValue
? 我有自己的解决方案,但想看看其他人会如何做。

val i1: Option[Int] = ...
val i2: Option[Int] = ...
val defaultValue: Int = ...
val minValue = ?
scala max min scala-option
10个回答
9
投票

更新:我刚刚注意到我下面的解决方案和您答案中的解决方案表现不同 - 我将您的问题理解为要求两个值中的最小值当有两个值时,但在您的答案中您正在有效地对待

 None
就好像它包含一个比其他值更大(对于
min
)或更小的值(对于
max
)。

更具体地说:如果

i1
Some(1)
并且
i2
None
,我的解决方案将返回默认值,而你的解决方案将返回 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

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


6
投票

我使用以下方法解决了类似的问题。当两个选项都有值时,我们会处理特殊情况,否则我们使用 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
,因为排序需要比简单地查找最大值或最小值更多的处理(尽管在这种情况下可能没有太大区别)。


3
投票

我们可以将 2 个

Option
组合为
Iterable
Option
++
运算符,这允许我们使用
minOption
(以很好地处理由
None/None
情况),如有必要,可使用
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

2
投票
val minValue: Int = List(i1, i2).flatten.sorted.headOption getOrElse defaultValue

1
投票

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

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

但不如 List.flatten 方法。


1
投票

未提及的另一个选项是使用

reduceLeftOption
(根据需要互换
math.max
math.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

0
投票

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

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

0
投票

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
类型的一个值。 这里我们使用std cats
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]
。在我们的例子中,我们为
min
max
情况编写了两个不同的组合器:

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
.

总之,您可以在导入一些catssyntax

instances
后为任何类型(不仅是Int)定义自定义semigroupcombine此类型的选项。


0
投票

由于如果

Seq[Option[Element]].min
(无)为
None
,则
Seq[Option[Element]].exists(_.isEmpty)
计算为
true
,而如果
Seq[Option[Element]].max
为真,则
Some(element)
计算为
Seq[Option[Element]].exists(_.nonEmpty)
,所以我采用双重回归,结果找到
 min
通过使用
maxBy
:

println((for {
  a <- Seq(None, Some(3))
  b <- Seq(None, Some(4))
} yield Seq(a, b, Seq(a, b).maxBy(_.map(-_)).getOrElse(-666))
  .mkString(" -> ")
  ).mkString("\n")
)

结果输出为:

None -> None -> -666
None -> Some(4) -> 4
Some(3) -> None -> 3
Some(3) -> Some(4) -> 3

另一个解决方案是调整/实施

Ordering[Option[T]]

© www.soinside.com 2019 - 2024. All rights reserved.