Reputation: 889
I want to map a function on chained pairs of elements from a list. Consider the list:
a = [1,2,3,4,5]
By using e.g. the function f a1 a2 = a1 < a2
, I want to get:
[True,True,True,True]
The following seems to work in general:
zipWith f a (tail a)
However, I find it kind of hackish. Is there a more appropriate way, maybe using fold?
Upvotes: 1
Views: 180
Reputation: 1457
You could use mapAccumL
import Data.List
a = [1,2,3,4,5]
compareAdjacent = snd $ mapAccumL (\acc x -> (x,(x > acc))) 0 a
This produces the output [True,True,True,True,True]. If you want it to ignore the first list item, you could create a function like this:
compareAdjacent' (x:xs) = snd $ mapAccumL (\acc x -> (x,(x > acc))) x xs
compareAdjacent' [] = []
Upvotes: 1
Reputation: 7360
I think I can speak on behalf of the Haskell community if I tell you that that is the most idiomatic way of doing this in Haskell. You could hack something together using a fold but it is clearly not as readable as the first variant:
f p xs = snd $ foldl (\acc x -> (x, snd acc ++ [p (fst acc) x])) (head xs, []) (tail xs)
Thank you @WillNess for supplying a more readable version of above's function, although it still does not beat zipWith
in brevity and clarity.
f p = foldr g [] . tails where g (x:y:_) r = p x y:r; g _ _ = []
This is really hackish. The problem with using a fold in this situation is that normally the elements will be dealt with separately and they do not "know" about their neighbours. The only values they know are the accumulator and their own.
In this example, doing f (<) [1,2,3,4,5]
will check if the left element of a pair is smaller than the one before, but it is way less readable than just doing:
f p xs = zipWith p xs (tail xs)
Usage would be the same as above.
Note that I am no professional in the Haskell language, so it may be that this approach using a fold could have been written in a better way, but nevertheless it will never beat the elegance of the zipWith
approach.
Upvotes: 6
Reputation: 101
You can use the Traversable instance over a State functor to achieve the expected result, like so :
traverseSt :: Traversable t => (a -> a -> b) -> a -> t a -> t b
traverseSt f x ta = runState (traverse (\a -> get >>= \a' -> f a a'<$put a) ta) x
You may then specialize this function to work on non-empty lists (since you need to know at least one element to provide the first seed value), which looks like :
zipSelf :: (a -> a -> b) -> [a] -> [b]
zipSelf f (a:as) = traverseSt f a as
zipSelf f [] = error "Impossible use of zipSelf on empty structure"
Splitting code in this way, you may also define zipSelf
over other structures, such as Tree
s or Maybe
s, or any type for which a first element is easily extractible.
Cheerio,
Upvotes: 3
Reputation: 54058
You could write a function like
movingApply :: (a -> a -> b) -> [a] -> [b]
movingApply f (x:y:xs) = f x y : movingApply f (y:xs)
movingApply f _ = []
(feel free to pick a better name, this one is terrible), but this is computationally the same as zipWith f x (drop 1 x)
when you expand it out. Note that I've used drop 1
instead of tail
, this is because tail
is a partial function, and tail []
results in a runtime error while drop 1 []
returns []
. Since this is pretty much the same thing as the zipWith
variety, I'd prefer zipWith
since it's a one-liner and I don't have to do the recursion manually.
Upvotes: 2