我有一个函数的以下开头,并且不确定我应该如何返回中间数(即既不是最大也不是最小的数字):
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
是中间数呢?还有2个守卫。如果c
是中间数呢?还有 2 名警卫,总共 6 种不同的情况。
(顺便说一句,这个表达| a < b && a > c = a
被称为守卫。如果你还没有牢牢掌握守卫是什么,那么我推荐LYAH #Guards)
当然有更好的方法来编写函数,但为了理解目的,最好能够手动和系统地分解所有可能的情况,并确定在每种情况下要做什么。How To Design Programs是一本很好的书,用于学习如何以这种方式进行系统化。
强制性的 Rube-Goldberg 答案:
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 案例。但是,当两个数字相等时会发生什么?那么中间的数字应该是重复的数字之一。
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
我做了一个快速的蛮力方法,但这肯定不是最好的解决方案
import Data.List
middleNum :: Int -> Int -> Int -> Int
middleNum a b c = (\[_,m,_] -> m) $ sort $ a:b:c:[]
显然这是一个糟糕的主意,因为它明确依赖于列表中有 3 个项目,但它确实完成了工作
您可以利用警卫并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