对列表中的每个第二个元素应用一个函数

Apply a function to every second element in a list

我想对列表中的每个第二个元素应用一个函数:

> mapToEverySecond (*2) [1..10]
[1,4,3,8,5,12,7,16,9,20] 

我编写了以下函数:

mapToEverySecond :: (a -> a) -> [a] -> [a]
mapToEverySecond f l = map (\(i,x) -> if odd i then f x else x) $ zip [0..] l

这行得通,但我想知道是否有更惯用的方法来做这样的事情。

我会这样做:

mapOnlyOddNumbered f []      = []
mapOnlyOddNumbered f (x:xs)  = f x : mapOnlyEvenNumbered f xs

mapOnlyEvenNumbered f []     = []
mapOnlyEvenNumbered f (x:xs) = x : mapOnlyOddNumbered f xs

这是否是 "idiomatic" 是一个见仁见智的问题(如果它适合那里,我会把它作为评论给出),但看到许多不同的方法可能会有用。您的解决方案与我的解决方案或评论中的解决方案一样有效,并且更容易更改为 mapOnlyEvery13ndmapOnlyPrimeNumbered

我写得不多Haskell,但首先想到的是:

func :: (a -> a) -> [a] -> [a]
func f [] = []
func f [x] = [x]
func f (x:s:xs) = x:(f s):(func f xs)

有点难看,因为你不仅要处理空列表,还要处理只有一个元素的列表。这也不能很好地扩展(如果你想要每三分之一,或者

可以像@Landei 指出的那样写

func :: (a -> a) -> [a] -> [a]
func f (x:s:xs) = x:(f s):(func f xs)
func f xs = xs

但是,恕我直言,为了摆脱对 [][x] 的丑陋检查,这会使其更难阅读(至少第一次如此)。

我可能会这样做:

mapToEverySecond f xs = foldr go (`seq` []) xs False
  where
    go x cont !mapThisTime =
      (if mapThisTime then f x else x) : cont (not mapThisTime)

但如果我正在编写库代码,我可能会将其包装成 build 形式。

编辑

是的,这也可以使用 mapAccumLtraverse 来完成。

import Control.Applicative
import Control.Monad.Trans.State.Strict
import Data.Traversable (Traversable (traverse), mapAccumL)

mapToEverySecond :: Traversable t => (a -> a) -> t a -> t a
-- Either
mapToEverySecond f = snd . flip mapAccumL False
 (\mapThisTime x ->
     if mapThisTime
     then (False, f x)
     else (True, x))

-- or
mapToEverySecond f xs = evalState (traverse step xs) False
  where
    step x = do
      mapThisTime <- get
      put (not mapThisTime)
      if mapThisTime then return (f x) else return x

或者你可以用 scanl 来完成,我会留给你去弄清楚。

这更多是对@MartinHaTh 的回答的评论。我会稍微优化他的解决方案

func :: (a -> a) -> [a] -> [a]
func f = loop
  where
    loop []  = []
    loop [x] = [x]
    loop (x:s:xs) = x : f s : loop xs
mapToEverySecond = zipWith ($) (cycle [id, (*2)])

是我能想到的最小的了,在我看来也很清晰。它还 kindan 次扩展。

编辑:哦,评论里已经有人推荐了。我不想偷,但我真的认为这就是答案。

不是很优雅,但这是我的看法:

mapToEverySecond f = reverse . fst . foldl' cmb ([], False) where
    cmb (xs, b) x = ((if b then f else id) x : xs, not b)

或改进 MartinHaTh 的回答:

mapToEverySecond f (x : x' : xs) = x : f x' : mapToEverySecond f xs
mapToEverySecond _ xs = xs