简体   繁体   中英

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:

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

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).

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

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.

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.

Edit

Yes, this can also be done using mapAccumL or 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

Or you can do it with scanl , which I'll leave for you to figure out.

This is more a comment to @MartinHaTh's answer. 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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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