可以使用foldr实现mapEvery

时间:2014-10-08 16:30:09

标签: haskell functional-programming fold

对于将函数映射到列表中每个第n个元素的函数:

mapEvery :: Int -> (a -> a) -> [a] -> [a]
mapEvery n f = zipWith ($) (drop 1 . cycle . take n $ f : repeat id)

是否可以像普通foldr一样使用map来实现这一点?

编辑:在标题中,更改了'文件夹'到' foldr'。自动更正...

2 个答案:

答案 0 :(得分:6)

这是一个解决方案

mapEvery :: Int -> (a -> a) -> [a] -> [a]
mapEvery n f as = foldr go (const []) as 1 where
  go a as m 
    | m == n    = f a : as 1
    | otherwise =   a : as (m+1)

这使用" foldl作为foldr"折叠时沿着列表从左向右传递状态的技巧。基本上,如果我们将foldr的类型读作(a -> r -> r) -> r -> [a] -> r,那么我们将r实例化为Int -> [a],其中传递的整数是我们在没有传递的元素的当前数量调用函数。

答案 1 :(得分:1)

是的,它可以:

mapEvery :: Int -> (a -> a) -> [a] -> [a]
mapEvery n f xs
    = foldr (\y ys -> g y : ys) []
    $ zip [1..] xs
    where
        g (i, y) = if i `mod` n == 0 then f y else y

而且,既然它是possible to implement zip in terms of foldr,如果你真的想要,你可以得到更多的折叠。这甚至适用于无限列表:

> take 20 $ mapEvery 5 (+1) $ repeat 1
[1,1,1,1,2,1,1,1,1,2,1,1,1,1,2,1,1,1,1,2]

这就是更多foldr和内联g的情况:

mapEvery :: Int -> (a -> a) -> [a] -> [a]
mapEvery _ _ [] = []
mapEvery n f xs
    = foldr (\(i, y) ys -> (if i `mod` n == 0 then f y else y) : ys) []
    $ foldr step (const []) [1..] xs
    where
        step _ _ [] = []
        step x zipsfn (y:ys) = (x, y) : zipsfn ys

现在,我建议这样写吗?绝对不。这仍然是你可以得到的混淆,同时仍然写作"可读"码。但它确实证明了使用非常强大的foldr来实现相对复杂的功能是可能的。