列表生成通过模式匹配

时间:2017-08-30 15:25:37

标签: list haskell pattern-matching

我需要找到进展长度。当此进展的总和等于sumVal时。我们假设这个任务总是有答案。

findN :: Integer -> Integer
findN sumVal = n where
    sum [1,2..n] = sumVal

示例:

findN 55 --evaluates to 10

我的解决方案有错误

  

模式中的解析错误[1,2..n]

如何修复我的代码?模式匹配是否可以这样做?我不想做递归:遍历列表并累加总和,直到它不等于sumVal。

也许是另一种优雅的解决方案?

1 个答案:

答案 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 nDouble 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