对于将函数映射到列表中每个第n个元素的函数:
mapEvery :: Int -> (a -> a) -> [a] -> [a]
mapEvery n f = zipWith ($) (drop 1 . cycle . take n $ f : repeat id)
是否可以像普通foldr
一样使用map
来实现这一点?
编辑:在标题中,更改了'文件夹'到' foldr'。自动更正...
答案 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
来实现相对复杂的功能是可能的。