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

Calculate sum and length of a list simultaneously with foldl and a tuple

我已经为 foldr 苦苦挣扎了一段时间。我想将一个整数列表转换为一个包含列表总和和整数数量的元组。例如 [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列表中的每个元素。有什么修改建议吗?谢谢!

那么 foldl 的类型是 foldl :: (a -> b -> a) -> a -> [b] -> aa 是累加器的类型(这里是一个 2 元组),b 是列表中元素的类型.

但是在你的 lambda 表达式中 你写

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

注意这里的变量b是列表的一个元素,a是一个元组。但是这里省略了累加器。您总是将 sum1 添加到 b,但是由于 sum1 是一个常量,所以这没什么用。 len1 也是如此,因此,您将始终获得一个元组,其中包含列表的 last 元素作为第一项,而 1 作为第二项.

但是根据您的尝试,我认为您的目标是在 Haskell 中编写 "imperative" 代码。现在 Haskell 中的所有变量都是 不可变的 ,因此将 len1 设置为 0(在 where 子句中),将导致事实上 len1 始终是 0,等等。我们不更改累加器,实际上 foldr 是一个递归函数,每次调用具有不同值的参数,并在最后returns我们称之为“累加器”的参数。

因此我们可以将尝试更改为:

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

所以这里我们每次都将累加器与 (cursum, curlen) 进行模式匹配(包含“current”(到目前为止)总和和长度),并且每次我们构造包含 "new" 当前总和(旧总和与 b 的总和)和 "new" 当前长度(长度加一)的新元组。

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

所以现在我们得到:

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

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

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)

我们在任何时候都不会修改任何变量,只是通过组合 current 部分来计算总和的 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)))