简体   繁体   English

将函数应用于列表中的每个第二个元素

[英]Apply a function to every second element in a list

I'd like to apply a function to every second element in a list: 我想将一个函数应用于列表中的每个第二个元素:

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

I've written the following function: 我写了以下函数:

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

This works, but I wonder if there is a more idiomatic way to do things like that. 这有效,但我想知道是否有更惯用的方法来做这样的事情。

I haven't written very much Haskell, but here's the first thing that came into mind: 我没有写过很多Haskell,但这是我想到的第一件事:

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

It is a little ulgy, since you have to not only take care of the empty list, but also the list with one element. 这有点过分,因为你不仅需要处理空列表,还需要处理带有一个元素的列表。 This doesn't really scale well either (what if you want every third, or 这也不能很好地扩展(如果你想要每三分之一,或者

One could do as @Landei points out, and write 人们可以这样做,因为@Landei指出并写道

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

In order to get rid of the ugly checks for both [] and [x] , though, IMHO, this makes it a little harder to read (at least the first time). 然而,为了摆脱对[][x]的丑陋检查,这使得它更难以阅读(至少第一次)。

Here is how I would do it: 我将如何做到这一点:

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

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

Whether this is "idiomatic" is a matter of opinion (and I would have given it as a comment if it would fit there) , but it may be useful to see a number of different approaches. 这是否是“惯用的”是一个意见问题(如果它适合那里我会把它作为评论),但看到许多不同的方法可能是有用的。 Your solution is just as valid as mine, or the ones in the comments, and easier to change into say mapOnlyEvery13nd or mapOnlyPrimeNumbered 您的解决方案与我的解决方案或评论中的解决方案一样有效,并且更容易更改为mapOnlyEvery13ndmapOnlyPrimeNumbered

mapToEverySecond = zipWith ($) (cycle [id, (*2)])

Is the smallest I can think of, also looks pretty clear in my opinion. 我能想到的是最小的,在我看来也很清楚。 It also kinda scales with every n th. 它也有点与每n个比例。

Edit: Oh, people already suggested it in comments. 编辑:哦,人们已在评论中提出建议。 I don't want to steal it, but I really think this is the answer. 我不想偷它,但我真的认为这就是答案。

Here's how I would probably do it: 这是我可能会这样做的:

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

But if I were writing library code, I'd probably wrap that up in a build form. 但是如果我正在编写库代码,我可能会以build形式将其包装起来。

Edit 编辑

Yes, this can also be done using mapAccumL or traverse . 是的,这也可以使用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

Or you can do it with scanl , which I'll leave for you to figure out. 或者你可以使用scanl来完成它,我将留给你弄清楚。

This is more a comment to @MartinHaTh's answer. 这更像是对@ MartinHaTh的答案的评论。 I'd slightly optimize his solution to 我稍微优化了他的解决方案

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

Not very elegant, but this is my take: 不是很优雅,但这是我的看法:

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

Or improving on MartinHaTh's answer: 或改进MartinHaTh的答案:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM