问题描述
我已经实现了一个函数 (!!=),它给出了一个列表和一个包含列表中索引的元组和一个 新值,用给定的新值更新给定列表 索引。
(!!=) :: [a] -> (Int,a) -> [a]
(!!=) xs (0,a) = a : tail xs
(!!=) [] (i,a) = error "Index not in the list"
(!!=) (x:xs) (i,a) = x : xs !!= (i-1,a)
作为折叠概念的初学者,我想知道是否有办法使用 foldl 或 foldr 来实现相同的结果?
非常感谢。
解决方法
我会给你我认为更容易理解的 foldl
版本,也是我能想到的最简单/最直接的版本。
但请注意,您不应该使用 foldl
(使用 foldl'
: https://wiki.haskell.org/Foldr_Foldl_Foldl') - 您也不应该像这样使用 ++
(使用 :
和后反转);)
反正就是这个想法:
(!!=) xs (i,a) = snd $ foldl
(\(j,ys) x -> (j+1,if j == i then ys ++ [a] else ys ++ [x]))
(0,[])
xs
- 作为折叠的状态/累加器,我采用当前索引和累加结果列表的元组(因此
snd
因为我最终只想要这个)立> - 然后折叠函数只需要查看我们是否在索引处并交换元素 - 返回下一个索引和新的累积列表
作为练习,您可以尝试:
- 使用
:
代替++
和reverse
- 重写为
foldr
- 查看
zipWith
并使用 this (zipWith (...) [0..] xs
) 而不是 fold 重写它(这类似于使用带有索引的 map
foldl
和 foldr
都不能有效地完成这项特定的工作(除非您在折叠列表时通过模式匹配“作弊”),尽管 {{ 1}} 可以做得不那么糟糕。不,您真正需要的是不同风格的弃牌,有时称为foldr
:
para
para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para _f n [] = n
para f n (a : as) = f a as (para f n as)
与 para
非常相似。它们中的每一个都接受一个组合函数,并且对于每个元素,传递该元素的组合函数以及折叠列表其余部分的结果。但是 foldr
添加了一些额外的东西:它也会传入列表的其余部分!因此,一旦到达替换点,就无需重建列表的尾部。
但是……您如何从para
或foldr
开始计数?这带来了一个经典的技巧,有时称为“高阶折叠”。不是 para
生成一个 list,而是生成一个 函数,将插入位置作为参数。
para go stop xs
请注意,(!!=) :: [a] -> (Int,a) -> [a]
xs0 !!= (i0,new) = para go stop xs0 i0
where
-- If the list is empty,then no matter what index
-- you seek,it's not there.
stop = \_ -> error "Index not in the list"
-- We produce a function that takes an index. If the
-- index is 0,we combine the new element with "the rest of the list".
-- Otherwise we apply the function we get from folding up the rest of
-- the list to the predecessor of the index,and tack on the current
-- element.
go x xs r = \i -> case i of
0 -> new : xs
_ -> x : r (i - 1)
很容易实现para
:
foldr
可能不太明显的是 foldr c = para (\a _ b -> c a b)
可以实现(非常低效的版本)foldr
:
para
以免您误会并认为para f n = snd . foldr go ([],n)
where
go x ~(xs,r) = (x : xs,f x xs r)
“比”para
“更好”,要知道当不需要额外的功能时,{{1} } 使用起来更简单,通常会被编译成更高效的代码。