对列表中的每个第二个元素应用一个函数
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" 是一个见仁见智的问题(如果它适合那里,我会把它作为评论给出),但看到许多不同的方法可能会有用。您的解决方案与我的解决方案或评论中的解决方案一样有效,并且更容易更改为 mapOnlyEvery13nd
或 mapOnlyPrimeNumbered
我写得不多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
形式。
编辑
是的,这也可以使用 mapAccumL
或 traverse
来完成。
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)])
是我能想到的最小的了,在我看来也很清晰。它还 kinda 每 n 次扩展。
编辑:哦,评论里已经有人推荐了。我不想偷,但我真的认为这就是答案。
不是很优雅,但这是我的看法:
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
我想对列表中的每个第二个元素应用一个函数:
> 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" 是一个见仁见智的问题(如果它适合那里,我会把它作为评论给出),但看到许多不同的方法可能会有用。您的解决方案与我的解决方案或评论中的解决方案一样有效,并且更容易更改为 mapOnlyEvery13nd
或 mapOnlyPrimeNumbered
我写得不多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
形式。
编辑
是的,这也可以使用 mapAccumL
或 traverse
来完成。
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)])
是我能想到的最小的了,在我看来也很清晰。它还 kinda 每 n 次扩展。
编辑:哦,评论里已经有人推荐了。我不想偷,但我真的认为这就是答案。
不是很优雅,但这是我的看法:
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