Local maxima of list using fold

You could, but itd be very ugly. Mostly because to find the local maximum, you'll need to know the previous and next elements.

The previous element is no problem, but a fold isn't supposed to know anything about the elements after it.

As a possible approach

 import Data.List (zip3)

 localMax xs = map proj2
               . filter isLocalMax
               $ zip3 xs (tail xs) (drop 2 xs)
   where isLocalMax (x, y, z) = x < y && z < y
         proj2 (_,x,_) = x

So we shift the list by one and two and zip it all together so [a, b, c, d, e] becomes [(a, b, c), (b, c, d), (c, d, e)] then we just use map and filter to select the appropriate elements.

Do note though that map and filter could be smashed into one foldr if you really wanted to.


I'll try to only use the fold, as you asked. What about something like this?

lmax (x:y:xs) = third $ foldl f (x,y,[]) xs
  where
    third (_, _, x) = x
    f (x, y, ls) z = (y, z, if y > x && y > z then y:ls else ls)

The idea is that you pass a tuple in the fold instead of a list of results. The tuple (a triple) will contain the last two elements and the list of results. The function evaluates whether the second element of the triple is a local minimum w.r.t. the first element (its predecessor) and the current element passed by the fold (its successor).

ghci> lmax [1,3,2]
[3]
ghci> lmax [3,4,2,5,1,7,6,1,9]
[7,5,4]
ghci> lmax [1..10]
[]
ghci> lmax []
*** Exception: test.hs:(4,1)-(5,66): Non-exhaustive patterns in function lmax

Anyway, from this it should be easy to use whatever method you prefer in order to return an empty result list when the input list is too short.

Please note that, by using foldl, every new result is appended at the top. Because of this, the list of results is reversed. You might want to reverse again lmax's results if you want to have them in the same order as in the original list: lmax' = reverse . lmax.


localMaxAsUnfold :: [Int] -> [Int]
localMaxAsUnfold = unfoldr work
  where
    work (x:y:z:rest)
      | y > x && y > z = Just (y, y:z:rest)
      | otherwise      = work (y:z:rest) -- cheat and recurse internally
    work _             = Nothing

localMaxAsFold :: [Int] -> [Int]
localMaxAsFold = foldr work [] . makeTriples
  where
    makeTriples :: [Int] -> [(Int, Int, Int)]
    makeTriples vals = zip3 vals (tail vals) (drop 2 vals)

    work (x,y,z) vals
      | y > x && y > z = y : vals
      | otherwise      = vals