我有以下函数的开头,不确定该如何返回中间数字(即既不是最大值也不是最小值的数字):
middleNumber :: Int -> Int -> Int -> Int
middleNumber a b c
| ...
middleNumber a b c
| a < b && a > c = a
检查 a
是否为中间数,即小于 b
但大于 c
。
现在如果 a
是中间数,但实际上它比 b
大且比 c
小怎么办?这里有另一个保护条件。如果 b
是中间数呢?还有另外两个保护条件。如果 c
是中间数呢?那就有了总共6种不同的情况需要考虑。
(顺便说一下,表达式 | a < b && a > c = a
被称为保护条件。如果您还没有对保护条件有很好的掌握,我建议您看看 LYAH # Guards)
当然,有更好的编写函数的方法,但为了理解目的,手动系统地分解所有可能的情况并确定在每种情况下该做什么是很好的。 如何设计程序 是学习如何以这种方式系统化的好书。
我建议您将该函数分为两个步骤:首先对三个数字进行排序,然后取中间元素。对于第一步,还要考虑是否可以逐步进行;每一步都使其更接近完全排序,然后进行尾递归以使其更加接近。
import Control.Applicative
middleNumber a b c = sum $ [sum, negate.minimum, negate.maximum] <*> [[a,b,c]]
[编辑]
这里是另一种版本:
middleNumber a b c = fst $ maximumBy (compare `on` abs.snd) [(a,b-c),(b,c-a),(c,a-b)]
我相信我们可以将这个转换为箭头语法来进一步混淆,但这个任务留给感兴趣的读者。
在丹·伯顿(Dan Burton)的答案上,通过使用保护条件来扩展,我对a、b、c三种情况分别进行了评估。但是,如果其中2个数字相等会发生什么?那么中间的数字应该是其中一个重复的数字。
middleNumber :: Int -> Int -> Int -> Int
middleNumber a b c
| (a > b && a < c) || (a > c && a < b) = a
| (b > a && b < c) || (b > c && b < a) = b
| (c > a && c < b) || (c > b && c < a) = c
| otherwise = if a == b then a else c
您可以利用守卫和where
以简单的方式获得相同的结果:
middleNumber :: Int -> Int -> Int -> Int
middleNumber x y z
| a == x = max y z
| a == y = max x z
| a == z = max x y
where
a = max x $ max y z
如果您无法访问内置的max
函数,您可以轻松地编写自己的函数。
max' :: Int -> Int -> Int
max' x y
| x > y = x
| otherwise = y
我使用了一个快速的暴力方法,但这绝对不是最好的解决方案
import Data.List
middleNum :: Int -> Int -> Int -> Int
middleNum a b c = (\[_,m,_] -> m) $ sort $ a:b:c:[]
显然,这是一个糟糕的想法,因为它明确依赖于列表中有3个项目,但它能够完成任务。
middleNumber a b c = minimum [max a b, max a c, max b c]
(反过来也成立) - sclv