Its very common where I come across some list of elements xs
and want to do something to do something with every Nth element. The simplest example would be the Sieve or Erastothenes, where you want to "knock out" every multiple of a given prime. The two ways I could do this would be explicit recursion passing along a counter variable; or zipWith ($) (cycle (replicate (n-1) id ++ f))
. So which way is better/more elegant/more commonly used, or is there some library function like mapEveryN :: (a -> a) -> Int -> [a] -> [a]
that I haven't found?
Asked
Active
Viewed 489 times
6

Will Ness
- 70,110
- 9
- 98
- 181

Ramith Jayatilleka
- 2,132
- 16
- 25
-
3Well, you could do `zipWith ($) (drop 1 $ cycle $ f : replicate (n - 1) id)`, which seems to be pretty elegant to me. There's no explicit recursion, you avoid concatenation and instead use `:`, and it's a 1-liner. – bheklilr Sep 25 '14 at 14:19
-
1You can also obtain an intermediate list with `piecesOf n = unfoldr (Just . splitAt n)` then `concatMap` through it with whatever. Probably not as short and efficient as other methods but can be convenient sometimes. – n. m. could be an AI Sep 25 '14 at 14:23
-
@n.m. `piecesOf` creates groups of `n`, followed by a group that may be less than `n`, followed by an infinite number of empty groups. – pat Sep 25 '14 at 14:28
-
@pat yeah, it's for infinite lists... why would anyone settle for less? :) – n. m. could be an AI Sep 25 '14 at 14:49
-
this is probably not the best way to implement the sieve of eratosphenes, but what you've got is pretty good as a "apply to every n-th" solution, although I'd do it @bheklilr style – Sassa NF Sep 25 '14 at 14:49
-
I would say that recursion is the best way to do it. – user3329719 Sep 25 '14 at 16:13
-
Well, @bheklilr gave basically the way I did it. (Though I swapped `drop 1` for `tail`. If there's no better way, I'll go with that. – Ramith Jayatilleka Sep 25 '14 at 16:57
-
@RamithJayatilleka I personally just stick with `drop 1` over `tail` since `tail` is a partial function and I try not to use it. In this case, we're guaranteed that it'll succeed so it's fine to use, I've just gotten in the habit of ignoring the fact `tail` even exists. – bheklilr Sep 25 '14 at 16:59
-
Your original zipWith implementation is better than any of the suggested variants in my opinion. – Reid Barton Sep 25 '14 at 22:52
1 Answers
2
As you and bheklilr mention, cycle
provides a nice way to accomplish this. You can take advantage of laziness a bit though:
mapEvery :: Int -> (a -> a) -> [a] -> [a]
mapEvery n f = zipWith ($) (drop 1 . cycle . take n $ f : repeat id)
The use of zipWith
and cycle
seems more idiomatic (complex behavior composed from simpler behaviors) than a hand-written recursive function that combines both tasks.
Note: tail
here makes this function undefined for n = 0
so drop 1
is preferable.
There isn't a library function for this that I'm aware of.

Rein Henrichs
- 15,437
- 1
- 45
- 55
-
2Doing something to every 0th element of a list is probably nonsense, so it's better for the function to fail in that case. If it does happen to make sense in a particular context, you should figure out what the appropriate behavior is for n=0, not make the function total in some arbitrary way in the name of "safety". Luckily cycle [] is an error already, so changing tail to drop 1 has no effect. – Reid Barton Sep 25 '14 at 23:19
-
Nice work switching the `replicate` to `take`. I think that's cleaner, I'll use this. Thanks. – Ramith Jayatilleka Sep 26 '14 at 16:12