我需要找到进展长度。当此进展的总和等于sumVal时。我们假设这个任务总是有答案。
findN :: Integer -> Integer
findN sumVal = n where
sum [1,2..n] = sumVal
示例:
findN 55 --evaluates to 10
我的解决方案有错误
模式中的解析错误[1,2..n]
如何修复我的代码?模式匹配是否可以这样做?我不想做递归:遍历列表并累加总和,直到它不等于sumVal。
也许是另一种优雅的解决方案?
答案 0 :(得分:4)
这不是Haskell的工作原理。 Haskell不是声明性的。如果你写的东西如下:
patter = expression
因此,它需要左侧的模式和右侧的表达式。 sum [1,2..n]
不是一种模式:它是一种表达。
下一个=
不相等*:它是声明:这意味着您可以根据表达式定义模式。由于您已在函数的头部定义了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
。在这种情况下,程序将永远循环:它将不断更新总和,但永远不会找到总和为n
的{{1}}。我们可以通过构造一个返回14
的函数来解决这个问题:如果总和存在,我们返回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)
Num
:
Ord
将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
和Double
s,其中精度可能是一个问题。
最后解决方案效率不高。我们知道这样的清单的总和是:
Float
这意味着:
n
---
\ n * (n+1)
/ i = --------- = s
--- 2
i=0
因此我们计算 ________
V 8 s + 1 - 1
n = -------------
2
的平方根,计算它是否为奇数整数根,然后从中减去一并将其除以2得到8 * s + 1
。如果根不是整数或奇数,我们应该返回n
。
对于Nothing
,我们可以使用Haskell Wiki上提供的整数平方根:
Integer
然后我们可以使用:
(^!) :: 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