这不是 Haskell 的工作方式。 Haskell 不是那种声明性的。如果你写这样的东西:
patter = expression
因此它期望左边有一个模式,右边有一个表达式。 sum [1,2..n] 不是一个模式:它是一个表达式。
下一个= not 相等*:它是一个声明:这意味着您根据表达式定义模式。由于您已经在函数头部定义了sumVal,因此您不能在where 子句中再次定义它。
那么我们该如何解决呢?我们可以构造一个递归函数,它构造一个运行总和,直到它达到请求的总和sumVal,然后返回它添加到它的最后一个数字。喜欢:
findN :: Integer -> Integer
findN sumVal = go 0 0
where go n s | s == sumVal = n
| otherwise = go (n+1) (s+n+1)
因此,如果我们想找到一个总和的n,我们调用go 0 0,第一个0是我们添加到总和的最后一个元素,第二个0是迄今为止获得的总和。每次我们检查运行总和 s 是否等于 sumVal。如果是这种情况,我们返回最后添加的元素n。如果总和不相等,我们使用go (n+1) (s+n+1) 执行递归:我们更新添加到n+1 的元素,并对运行总和执行此操作。
上述解决方案不是很安全,也不是通用的。出现的第一个问题是我们可能会使用我们永远无法获得的sumVal 调用函数:例如findN 14。在这种情况下,程序将永远循环:它将不断更新总和,但永远找不到总和为14 的n。我们可以通过构造一个返回Maybe Integer的函数来解决这个问题:如果和存在,则返回Just n,否则返回Nothing。
如果运行总和大于请求的总和,我们知道总和不存在。所以我们可以把代码改成:
findN :: Integer -> Maybe Integer
findN sumVal = go 0 0
where go n s | s == sumVal = Just n
| s > sumVal = Nothing
| otherwise = go (n+1) (s+n+1)
我们还可以推广该函数以处理所有可以订购的数字类型NumOrd:
findN :: (Num n, Ord n) => n -> Maybe n
findN sumVal = go 0 0
where go n s | s == sumVal = Just n
| s > sumVal = Nothing
| otherwise = go (n+1) (s+n+1)
将Integral n 添加为类型约束可能是一个想法,以防止我们在精度可能存在问题的情况下使用Doubles 和Floats。
最后解决方案不是很有效。我们知道这样的列表的总和是:
n
---
\ n * (n+1)
/ i = --------- = s
--- 2
i=0
也就是说:
________
V 8 s + 1 - 1
n = -------------
2
所以我们计算8 * s + 1的平方根,计算它是否是奇数的整数根,然后将其减一除以二得到n。如果根不是整数或奇数,我们应该返回Nothing。
对于Integers,我们可以使用Haskell Wiki 上的整数平方根:
(^!) :: Num a => a -> Int -> a
(^!) x n = x^n
squareRoot :: Integer -> Integer
squareRoot 0 = 0
squareRoot 1 = 1
squareRoot n =
let twopows = iterate (^!2) 2
(lowerRoot, lowerN) =
last $ takeWhile ((n>=) . snd) $ zip (1:twopows) twopows
newtonStep x = div (x + div n x) 2
iters = iterate newtonStep (squareRoot (div n lowerN) * lowerRoot)
isRoot r = r^!2 <= n && n < (r+1)^!2
in head $ dropWhile (not . isRoot) iters
那么我们可以使用:
findN :: Integer -> Maybe Integer
findN 0 = Just 0
findN sumVal | r * r == sq && odd r = Just (div (r-1) 2)
| otherwise = Nothing
where sq = 8*sumVal+1
r = squareRoot sq
这会产生:
*Main> findN 0
Just 0
*Main> findN 1
Just 1
*Main> findN 2
Nothing
*Main> findN 3
Just 2
*Main> findN 4
Nothing
*Main> findN 5
Nothing
*Main> findN 6
Just 3
*Main> findN 55
Just 10