【发布时间】:2012-09-28 12:22:08
【问题描述】:
您如何在下面找到minValue?
我有自己的解决方案,但想看看其他人会怎么做。
val i1: Option[Int] = ...
val i2: Option[Int] = ...
val defaultValue: Int = ...
val minValue = ?
【问题讨论】:
您如何在下面找到minValue?
我有自己的解决方案,但想看看其他人会怎么做。
val i1: Option[Int] = ...
val i2: Option[Int] = ...
val defaultValue: Int = ...
val minValue = ?
【问题讨论】:
更新:我刚刚注意到我下面的解决方案和您的答案中的解决方案表现不同 - 我将您的问题读作要求两个值中的最小值当有两个值时,但在您的回答你有效地对待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
在这种情况下,标准库解决方案并没有那么优雅,但这是一个有用的抽象知识。
【讨论】:
我使用以下方法解决了类似的问题。我们处理两个选项都有值时的特殊情况,否则我们使用 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
}
【讨论】:
我想这就是你所追求的:
val minValue = List(i1, i2).flatten match {
case Nil => defaultValue
case xs => xs.min
}
我会避免使用sorted,因为排序需要比简单地找到最大值或最小值更多的处理(尽管在这种情况下它可能没有太大区别)。
【讨论】:
((xs:List[Int]) => {if (xs == Nil) defaultValue else xs.min})(List(i1, i2).flatten),但为了简洁起见,这显然是一种混淆。
List(Option(defaultValue), i1, i2).flatten.min?
val minValue: Int = List(i1, i2).flatten.sorted.headOption getOrElse defaultValue
【讨论】:
你可以在 for 表达式中使用模式,不匹配模式的值将被丢弃。
(for (Some(x) <- List(None, Some(3))) yield x) max
虽然不如 List.flatten 方法。
【讨论】:
另一个未提及的选项是使用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
【讨论】:
如果您想避免使用 scalaz 和 map/for/getOrElse,您可以执行以下操作:
val minValue = (i1, i2) match {
case (Some(x), Some(y)) => math.min(x, y)
case _ => defaultValue
}
【讨论】:
您可以使用自定义 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]。在我们的例子中,我们为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.
总之,您可以为任何类型(不仅是Int)定义自定义semigroup,并combine这种类型的选项在导入一些 cats syntax 和 instances 之后。
【讨论】: