Haskell-汇总列表的前n个元素

时间:2018-07-11 07:10:45

标签: haskell

我是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

但这不起作用...怎么了?

4 个答案:

答案 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,这很奇怪:在您的函数中,takeInt,而$基本上会将您的语句写到{{ 1}}。因此,您显然想执行以(x + take) (sumList xs)(一个(x + take))为函数,以Int作为参数的函数应用程序。但是sumList xs不是函数,因此它不进行类型检查,也不包含任何限制数字的逻辑。

所以基本上我们应该考虑三种情况:

  1. 空列表,在这种情况下,总和为Int;
  2. 要采用的元素数小于或等于零,在这种情况下,总和为0;和
  3. 要获取的元素数量大于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