使用foldl和元组同时计算列表的总和和长度

时间:2018-04-25 21:20:49

标签: haskell fold

我现在已经和foldr挣扎了一段时间。我想将一个int列表转换为一个元组,其中包含列表和int数的总和。例如[1,2,3,4] - > (10,4) 我在下面的函数迭代通过列表罚款,但只输出最后一个元素作为x val和1作为y val。

toTuple xs = let tuple = foldl (\a b -> ((sum1 + b),(len1 + 1))) (0.0,0.0) xs 
              in tuple   
               where sum1 = 0
                     len1 = 0  

我最初将sum1和len1作为一个函数,它将一个int作为一个输入但是没有'工作要么所以我将它们更改为初始化为0的变量。但是它似乎在列表中的每个元素上设置sum和len为0。有什么建议可以修改吗?谢谢!

2 个答案:

答案 0 :(得分:6)

foldl的类型为foldl :: (a -> b -> a) -> a -> [b] -> a,其中a为累加器的类型(此处为2元组),b列表中的元素类型。< / p>

但是在 lambda表达式中你写了

\a b -> ((sum1 + b),(len1 + 1))

请注意,此处变量b是列表的元素,a是元组。但是这里你省略了累加器。您始终将sum1添加到b,但由于sum1是常量,因此无用。 len1的结果相同,因此,您将始终获得一个元组,其中包含列表的 last 元素作为第一项,1作为第二项。

但是根据你的尝试,我认为你以某种方式打算写出'#34;命令式的&#34; Haskell中的代码。现在在Haskell中,所有变量都是不可变的,因此将len1设置为0(在where子句中)将导致len1这一事实总是0等等。我们不更改累加器,实际上foldr是一个递归函数,每次用不同值的参数调用自身,最后返回我们称为&的参数#34; 累加器&#34;

我们可以改变尝试:

toTuple = (Fractional s, Fractional n) => [s] -> (s, n)
toTuple = foldl (\(cursum, curlen) b -> (cursum + b, curlen + 1)) (0.0,0.0)

所以这里我们每次模式都将累加器与(cursum, curlen)匹配(包含&#34; 当前&#34;(到目前为止)总和和长度),每次我们构造一个包含&#34; new&#34;的新元组。当前总和(旧金额和b的总和)和&#34; new&#34;当前长度(长度增加1)。

但它仍然不是很优雅:这里初始元组的值为(0.0, 0.0),但结果是,你告诉Haskell这是一个2元组,其中两个元素都是Fractional s。虽然没有舍入错误,但这可能会有效,为什么要将此限制为Fractional s?例如,如果您有一个Integer列表,我们可以将它们相加而不会出现任何舍入错误。通过使用(0, 0)作为初始元组,我们使元组成为一个2元组,其中两个元素都具有属于Num类型类的类型:所以数字。请注意,这两个本身具有相同的Num类型,这种限制较少。

所以现在我们得到了:

toTuple = (Num s, Num n) => [s] -> (s, n)
toTuple = foldl (\(cursum, curlen) b -> (cursum + b, curlen + 1)) (0, 0)

答案 1 :(得分:6)

看起来你还没有建立褶皱的直觉。您可以将foldl combine start input之类的折叠视为从某个start值开始,然后使用input函数将该值与combine的每个元素相结合。该函数接受当前“状态”和列表的下一个元素,并返回更新的状态。所以你非常接近一个有效的解决方案:

toTuple xs = foldl (\ (sum, len) x -> (sum + x, len + 1)) (0, 0) xs

逐步执行像[1, 2, 3, 4]这样的示例输入,状态(sum, len),通常称为“累加器”,每次调用折叠函数时都会采用以下值:

(0, 0)
(0+1, 0+1)             = (1, 1)
(0+1+2, 0+1+1)         = (3, 2)
(0+1+2+3, 0+1+1+1)     = (6, 3)
(0+1+2+3+4, 0+1+1+1+1) = (10, 4)

我们不会修改任何变量,只需通过组合当前部分和&amp;来计算总和的 next 值和长度。列表中每个元素的长度。对于从左到右完成的左折(foldl);对于右折(foldr)它是从右到左,所以参数的顺序((sum, len)x)是相反的:

toTuple xs = foldr (\ x (sum, len) -> (sum + x, len + 1)) (0, 0) xs

但是,对于右侧折叠,我发现更容易将它们视为使用函数替换列表中的所有:并将[]替换为值:

foldr f z [1, 2, 3, 4]
foldr f z (1 : (2 : (3 : (4 : []))))
1 `f` (2 `f` (3 `f` (4 `f` z)))