Haskell递归 - 找到列表中数字之间的最大差异

时间:2014-02-05 22:57:37

标签: algorithm haskell recursion

这就是手头的问题:我需要使用递归找到列表中相邻数字之间的最大差异。以下面的列表为例:[1,2,5,6,7,9]。两个相邻数字之间的最大差异是3(2到5之间)。

我知道递归可能不是最佳解决方案,但我正在努力提高我在Haskell中使用递归的能力。

这是我目前拥有的当前代码:

largestDiff (x:y:xs) = if (length (y:xs) > 1) then max((x-y), largestDiff (y:xs)) else 0

基本上 - 列表将一直变短,直到达到1(即不能再比较数字,然后返回0)。当0向上调用调用堆栈时,max函数随后用于实现“山丘之王”类型算法。最后 - 在调用堆栈的末尾,应返回最大的数字。

麻烦的是,我的代码中出现了一个我无法解决的错误:

Occurs check: cannot construct the infinite type:
  t1 = (t0, t1) -> (t0, t1)
In the return type of a call of `largestDiff'
Probable cause: `largestDiff' is applied to too few arguments
In the expression: largestDiff (y : xs)
In the first argument of `max', namely
  `((x - y), largestDiff (y : xs))'

任何人都有一些智慧的话语可以分享?

谢谢你的时间!

编辑:感谢大家的时间 - 经过多次试验和错误,我最终独立地发现了一种更为简单的方法。

largestDiff [] = error "List too small"
largestDiff [x] = error "List too small"
largestDiff [x,y] = abs(x-y)
largestDiff (x:y:xs) = max(abs(x-y)) (largestDiff (y:xs))

再次感谢,全部!

3 个答案:

答案 0 :(得分:3)

因此,您的代码抛出错误的原因是因为

max((x-y), largestDiff (y:xs))

在Haskell中,你不要在参数周围使用括号并用逗号分隔它们,正确的语法是

max (x - y) (largestDiff (y:xs))

您使用的语法被解析为

max ((x - y), largestDiff (y:xs))

看起来你正在将一个元组传递给max

然而,这并没有解决问题。我总是回到0。相反,我建议将问题分解为两个功能。你想计算差值的最大值,所以先写一个函数来计算差值,然后用一个函数来计算它们的最大值:

diffs :: Num a => [a] -> [a]
diffs [] = []                            -- No elements case
diffs [x] = []                           -- One element case
diffs (x:y:xs) = y - x : diffs (y:xs)    -- Two or more elements case

largestDiff :: (Ord a, Num a) => [a] -> a
largestDiff xs = maximum $ map abs $ diffs xs

请注意我是如何将递归拉到最简单的情况下的。当我们遍历列表时,我们不需要计算最大值;它可能,更复杂。由于Haskell有一个方便的内置函数来计算我们的列表最大值,我们也可以利用它。我们的递归函数简洁明了,然后与maximum结合使用以实现所需的largestDiff。作为一个FYI,diffs实际上只是计算数字列表的导数的函数,它可以是一个非常有用的数据处理函数。

编辑Ord上需要largestDiff约束,并在计算最大值之前添加到map abs

答案 1 :(得分:1)

这是我的看法。

首先是一些助手:

diff a b = abs(a-b)
pick a b = if a > b then a else b

然后是解决方案:

mdiff :: [Int] -> Int
mdiff [] = 0
mdiff [_] = 0
mdiff (a:b:xs) = pick (diff a b) (mdiff (b:xs))

您必须提供两个结束子句,因为序列可能包含偶数或奇数个元素。

答案 2 :(得分:1)

可以获得解决此问题的另一个解决方案 只需转换列表并折叠/缩小它们。

import Data.List (foldl')

diffs :: (Num a) => [a] -> [a]
diffs x = zipWith (-) x (drop 1 x)

absMax :: (Ord a, Num a) => [a] -> a
absMax x = foldl' max (fromInteger 0) (map abs x)

现在我承认这对于初学者来说有点密集,所以我将解释上面的内容。 函数zipWith使用二元函数转换两个给定列表, 在这种情况下,(-)

我们传递给zipWith的第二个列表是drop 1 x,这只是另一种方式 描述列表的尾部,但tail []导致错误, drop 1 []只生成空列表。所以drop 1是“更安全”的选择。

所以第一个函数计算相邻的差异。

第二个函数的名称表明它计算最大绝对值 给定列表的值,只是部分为真,如果传递给它,则结果为“0” 空列表。

但是这是如何发生的,从右到左阅读,我们看到map abs 将每个列表元素转换为其绝对值,由...声明 Num a约束。然后foldl' - 函数遍历列表并且 累加前一个累加器和当前元素的最大值 列表遍历。此外,我想提一下foldl'是“严格的” foldl - 函数的姐妹/兄弟,后者很少使用, 因为它倾向于建立一堆被称为thunks的未评估表达式。

所以,让我们放弃所有这些等等,看看它的实际效果; - )

> let a = diffs [1..3] :: [Int]
>>> zipWith (-) [1,2,3] (drop 1 [1,2,3])
<=> zipWith (-) [1,2,3] [2,3]
<=> [1-2,2-3] -- zipWith stops at the end of the SHORTER list
<=> [-1,-1]

> b = absMax a
>>> foldl' max (fromInteger 0) (map abs [-1,-1])
-- fromInteger 0 is in this case is just 0 - interesting stuff only happens
-- for other numerical types
<=> foldl' max 0 (map abs [-1,-1])
<=> foldl' max 0 [1,1]
<=> foldl' max (max 0 1) [1]
<=> foldl' max 1 [1]
<=> foldl' max (max 1 1) []
<=> foldl' max 1 [] -- foldl' _ acc [] returns just the accumulator
<=> 1