我是Haskell的新朋友。
假设我想用自己生成的函数对列表的前n个元素求和。我不知道该如何与Haskell合作。我只知道如何总结整个给定列表,例如
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs
例如,为了求和列表的前n个元素
从[1..10]中获取前5个数字,即1 + 2 + 3 + 4 + 5 = 15
我认为我可以做这样的事情:
sumList :: Int -> [Int] -> Int
sumList take [] = 0
sumList take (x:xs) = x + take $ sumList xs
但这不起作用...怎么了?
答案 0 :(得分:6)
因此,您知道如何对列表中的数字求和,
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs
,并且如果该列表中包含的元素不超过5个,则如果您确实打算在参数列表中添加不超过5个元素,则此函数甚至将返回正确的结果。通过重命名此函数,使我们的期望明确化
sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements [] = 0
sumUpToFiveElements (x:xs) = x + sumUpToFiveElements xs
对于五个以上的列表,它不会返回正确的结果,但至少名称正确。
我们可以解决这个问题吗?我们最多可以数5吗?我们还能像往前一样在输入列表中前进时最多计数5吗?
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
这当然不是正确的。我们现在进行计数,但是由于某种原因我们忽略了计数器。如果我们希望不超过5个元素,那么对计数器做出反应的正确时间是什么?让我们尝试counter == 5
:
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5 [] = 0
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
但是为什么我们要求列表达到5时也要为空?我们不要这样做:
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5 _ = 0 -- the wildcard `_` matches *anything*
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
成功!现在,当达到5时,我们停止计数!另外,我们也停止求和!!
等等,但是counter
的初始值是多少?我们没有指定它,因此函数用户(即我们自己)很容易犯错并使用不正确的初始值。顺便说一句, 正确的初始值是什么?
好的,让我们这样做:
sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements xs = go 1 xs -- is 1 the correct value here?
where
go counter _ | counter == 5 = 0
go counter [] = 0
go counter (x:xs) = x + go (counter + 1) xs
现在,我们没有多余的参数使我们的定义变得如此脆弱,因此容易出现用户错误。
现在是重点:
通用化! (通过将一个示例值替换为符号值;将5
更改为n
)。
sumUpToNElements :: Int -> [Int] -> Int
sumUpToNElements n xs = .......
........
完成。
另一个建议:在学习Haskell的初期,不要使用$
。使用 explicit 括号。
sumList take (x:xs) = x + take $ sumList xs
解析为
sumList take (x:xs) = (x + take) (sumList xs)
这将两个不相关的数字加在一起,然后将结果用作要用(sumList xs)
作为参数调用的函数(换句话说,这是错误的)。
如果使用显式括号,您可能不会那样写。
答案 1 :(得分:2)
您应该使用参数(最好是 not take
)限制值的数量,因为
是Prelude
的函数,因此限制了数字。
您的代码中的限制显然是take $ sumList xs
,这很奇怪:在您的函数中,take
是Int
,而$
基本上会将您的语句写到{{ 1}}。因此,您显然想执行以(x + take) (sumList xs)
(一个(x + take)
)为函数,以Int
作为参数的函数应用程序。但是sumList xs
不是函数,因此它不进行类型检查,也不包含任何限制数字的逻辑。
所以基本上我们应该考虑三种情况:
Int
; 0
;和0
,在这种情况下,我们将首部加到从尾部减去一个元素的总数中。一个简单的映射是:
0
但是您不需要自己编写这样的逻辑,您可以将内置的take :: Int -> [a] -> [a]
与sum :: Num a => [a] -> a
函数结合使用:
sumTakeList :: (Integral i, Num n) => i -> [n] -> n
sumTakeList _ [] = 0
sumTakeList t (x:xs) | t <= 0 = 0
| otherwise = x + sumTakeList (t-1) xs
现在,如果您需要对前五个元素求和,我们可以将其设为特例:
sumTakeList :: Num n => Int -> [n] -> n
sumTakeList t = sum . take t
答案 2 :(得分:1)
Hoogle是查看哪些功能可用以及如何工作的重要资源。这是take
上的页面以及the function you want的文档。
如您所见,名称take
被采用,但这是您可以用来实现此功能的功能。
请注意,您的sumList
需要另一个参数,即求和的元素数。您想要的语法类似于:
sumList :: Int -> [Int] -> Int
sumList n xs = _ $ take n xs
_
处为空白,您可以自己填写。它是Prelude中的一个函数,但是类型签名有些复杂,无法立即使用。
或者您也可以递归地编写它,包括两个基本情况和第三个累加参数(通过辅助函数):
sumList :: Int -> [Int] -> Int
sumList n xs = sumList' n xs 0 where
sumList' :: Int -> [Int] -> Int -> Int
sumList' 0 _ a = _ -- A base case.
sumList' _ [] a = _ -- The other base case.
sumList' m (y:ys) a = sumList' _ _ _ -- The recursive case.
在此处,等号左侧的_
符号应留在此处,这表示图案保护器将忽略该参数,但右侧的_
符号则为空白,供您填充在你自己。再次,GHC会告诉您所需的填充类型。
这种尾部递归函数是Haskell中非常常见的模式;您想确保每个递归调用都使您离基本情况更近了一步。通常,这将意味着从count参数中减去1来调用自身,或者以list参数的尾部作为新list参数来调用自身。在这里,您想两者都做。当您递归调用函数本身时,请不要忘记更新您的运行总和a
。
答案 3 :(得分:1)
这是一个简短而甜蜜的答案。你真的很亲近请考虑以下内容:
take
参数告诉您需要累加多少个元素,因此,如果您进行sumList 0 anything
,则应该始终得到0
,因为您没有任何元素。如果要使用第一个n
个元素,请将第一个元素加到总数中,然后计算下一个n-1
个元素的总和。
sumList 0 anything = 0
sumList n [] = 0
sumList n (e:es) = e + sumList (n-1) e