2014-10-08 51 views
3

实现对于列表中的一个函数映射到每个第n个元素的功能:可以mapEvery与foldr相似

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

是否有可能与foldr像普通map来实现这一点?

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

+0

我不会期望如此,至少不会有单一的折叠。 – 2014-10-08 16:38:00

+0

嗯?这真的很有趣。你将如何用多个'foldr'来实现它? – 2014-10-08 16:42:50

+0

...没关系,不要认为这也可以。使用'foldr',你只能计算你列表的_end_中有多少元素,而不是从一开始就有多少元素。 – 2014-10-08 16:45:02

回答

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) 

这将使用“foldlfoldr”招自左沿为你折列表右侧传状态。本质上,如果我们将foldr的类型读作(a -> r -> r) -> r -> [a] -> r,那么我们将r实例化为Int -> [a],其中传递的整数是我们未调用函数而传递的当前元素数。

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,你可以得到更多的折叠-Y,如果你真的想要的。这甚至适用于无限列表:

> 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来实现相对复杂的功能。