Haskell - 使用 foldl 或 foldr 而不是模式匹配来更新具有给定索引处的新值的列表

问题描述

我已经实现了一个函数 (!!=),它给出了一个列表和一个包含列表中索引的元组一个 新值,用给定的新值更新给定列表 索引。

(!!=) :: [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
,

foldlfoldr 都不能有效地完成这项特定的工作(除非您在折叠列表时通过模式匹配“作弊”),尽管 {{ 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 添加了一些额外的东西:它也会传入列表的其余部分!因此,一旦到达替换点,就无需重建列表的尾部。

但是……您如何从parafoldr 开始计数?这带来了一个经典的技巧,有时称为“高阶折叠”。不是 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} } 使用起来更简单,通常会被编译成更高效的代码。