简体   繁体   中英

Mapping a function on chained pairs of elements from a list

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 eg 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?

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.

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,

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 fx (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.

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' [] = []

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