这是我能想到的 max 的最小递归实现:
def max(xs: List[Int]): Option[Int] = xs match {
case Nil => None
case List(x: Int) => Some(x)
case x :: y :: rest => max( (if (x > y) x else y) :: rest )
}
它的工作原理是比较列表中的前两个元素,丢弃较小的元素(或第一个,如果两者相等),然后在剩余列表上调用自身。最终,这会将列表减少到一个必须是最大的元素。
我返回一个选项来处理给出空列表而不引发异常的情况 - 这迫使调用代码识别这种可能性并处理它(由调用者决定,如果they想要抛出异常)。
如果你想让它更通用,应该这样写:
def max[A <% Ordered[A]](xs: List[A]): Option[A] = xs match {
case Nil => None
case x :: Nil => Some(x)
case x :: y :: rest => max( (if (x > y) x else y) :: rest )
}
它将适用于任何扩展的类型Ordered
特征或存在隐式转换A
to Ordered[A]
在适用范围。所以默认情况下它适用于Int
, BigInt
, Char
, String
等等,因为 scala.Predef 为它们定义了转换。
我们可以变得更加通用,如下所示:
def max[A <% Ordered[A]](xs: Seq[A]): Option[A] = xs match {
case s if s.isEmpty || !s.hasDefiniteSize => None
case s if s.size == 1 => Some(s(0))
case s if s(0) <= s(1) => max(s drop 1)
case s => max((s drop 1).updated(0, s(0)))
}
它不仅适用于列表,还适用于向量和任何其他扩展的集合Seq
特征。请注意,我必须添加一个检查来查看序列是否实际上具有确定的大小 - 它可能是一个无限流,因此如果可能是这种情况,我们会后退。如果你确定你的流有一个确定的大小,你总是可以在调用这个函数之前强制它 - 无论如何它都会在整个流中工作。请参阅最后的注释了解为什么我really不想回来None
不过,对于无限流。我在这里这样做纯粹是为了简单起见。
但这不适用于集合和地图。该怎么办?下一个常见的超类型是Iterable
,但这不支持updated
或任何同等的东西。我们构造的任何东西对于实际类型来说都可能表现得很差。所以我的干净的无辅助函数递归失败了。我们could更改为使用辅助函数,但其他答案中有很多示例,我将坚持使用单简单函数方法。所以此时我们可以切换到reduceLeft
(当我们这样做时,让我们选择“Traversable”并满足all集合):
def max[A <% Ordered[A]](xs: Traversable[A]): Option[A] = {
if (xs.hasDefiniteSize)
xs reduceLeftOption({(b, a) => if (a >= b) a else b})
else None
}
但如果你不考虑reduceLeft递归,我们可以这样做:
def max[A <% Ordered[A]](xs: Traversable[A]): Option[A] = xs match {
case i if i.isEmpty => None
case i if i.size == 1 => Some(i.head)
case i if (i collect { case x if x > i.head => x }).isEmpty => Some(i.head)
case _ => max(xs collect { case x if x > xs.head => x })
}
它使用collect
组合器以避免一些笨拙的方法来避免新迭代器的出现xs.head
and xs drop 2
.
其中任何一个都可以安全地处理几乎任何有订单的集合。例子:
scala> max(Map(1 -> "two", 3 -> "Nine", 8 -> "carrot"))
res1: Option[(Int, String)] = Some((8,carrot))
scala> max("Supercalifragilisticexpialidocious")
res2: Option[Char] = Some(x)
我通常不会举其他例子,因为它需要更多的 Scala 专业知识。
另外,请记住基本的Traversable
特质提供了max
方法,所以这只是为了练习;)
注意:我希望我的所有示例都表明,仔细选择 case 表达式的顺序可以使每个单独的 case 表达式尽可能简单。
更重要的注意事项:哦,还有,虽然我回来时感到非常舒服None
对于输入Nil
,在实践中我强烈倾向于抛出异常hasDefiniteSize == false
。首先,有限流可以具有完全取决于评估顺序的确定或不确定的大小,并且该函数将有效地随机返回Option
在这些情况下 - 可能需要很长时间才能找到。其次,我希望人们能够区分是否通过了Nil
并通过了真正的风险输入(即无限流)。我才回来Option
在这些演示中,使代码尽可能简单。