6

Is there any way to find out local maxima of a list using foldr or foldl or maybe I have to use unfoldr because of first and last elements of list aren't local maximums?

I understand how to find it using recursion with guards, e.g.

localMax :: [Int] -> [Int]
localMax []       = []
localMax (x:[])   = []
localMax (x:y:[]) = []
localMax (x:y:z:zs)
    | y > z && y > x = y:localMax (y:z:zs)
    | otherwise = localMax (y:z:zs)
Will Ness
  • 70,110
  • 9
  • 98
  • 181
zerospiel
  • 632
  • 8
  • 20

8 Answers8

8

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.

daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
  • Thanks a lot. So, using «clear» folds to solve this problem are impossible? Your solution works perfectly. I'll try to smash `foldr` instead of `map` and `filter` – zerospiel Feb 26 '14 at 16:43
  • @zerospiel You can use something like `foldr (\x accum -> if isLocalMax x then x:accum else accum)` instead of `map` and `filter` but ti's harder to read – daniel gratzer Feb 26 '14 at 16:47
  • Also it'd be possible to combine `map` and `filter` into `mapMaybe`. – Petr Feb 26 '14 at 18:59
  • [it is quite easy](http://stackoverflow.com/a/22051606/849891) to emulate the insightful paramorphism with the shortsighted foldr, after first taking `init . tails`. :) – Will Ness Feb 26 '14 at 19:55
  • 1
    @WillNess I should have been more specific, when the user said "I have to use a `fold`" I assumed he meant something to the effect of `isSorted Pattern = foldr a b c` for some trivial value of `a` `b` and `c`. Which you can totally do, but involves dragging state through tuples and I personally don't like. Of course my answer can trivially be a fold and yours is quite nice as well, but both require some jiggery pokery on the list before the actual fold – daniel gratzer Feb 26 '14 at 20:00
  • yes, not a direct fold, that's for sure. Still, paramorhisms are just as basic a tool as foldr (cata), and emulating them is quite easy. – Will Ness Feb 26 '14 at 20:08
3

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.

Riccardo T.
  • 8,907
  • 5
  • 38
  • 78
  • Coding style is a little terse for something intended to teach but this solves the problem the OP was running into of how to get fold to handle the triple required. – Sean Perry Feb 26 '14 at 17:00
  • Also note the answer is backwards due to the use of foldl vs. foldr. – Sean Perry Feb 26 '14 at 17:06
  • True :) I removed the let by adding a more clear `third` function in order to simplify a bit, and I remarked the fact about reversed results. – Riccardo T. Feb 27 '14 at 08:56
  • you could fuse in the `reverse` with ``lmax (x:y:xs) = third $ foldl f (x,y,id) xs where { third (_, _, x) = x [] ; f (x, y, ls) z = if y > x && y > z then (y, z, ls.(y:)) else (y, z, ls) where {(f . g) x = let {y=g x} in y `seq` f y}}``. you also probably need to use `foldl'` instead of the `foldl` (which is almost always the case). – Will Ness Feb 11 '21 at 13:35
3
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
Sean Perry
  • 3,776
  • 1
  • 19
  • 31
3

we can always call the shortsighted1 foldr over (init . tails) to emulate the insightful paramorphism:

import Data.List
import Control.Applicative

localMaxima :: Ord a => [a] -> [a]
localMaxima = foldr g [] . init . tails . (zip <*> tail) 
   where
     g ((a,b):(_,c):_) r | a<b && b>c = b:r
     g  _              r              =   r

In this particular case init can be omitted. zip <*> tail (which is just a shorthand for \xs -> zip xs (tail xs)) forms a list of pairs of consecutive elements from the input list.

And no, I don't think it is particularly "ugly".2 :)

1 2 cf. this answer here
2 also another answer here

Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Thanks @WillNess, this was interesting. How do the various options presented to this question pan out in terms of performance? – Sean Perry Feb 26 '14 at 23:34
3

Idea is very close to what Will Ness suggested, but without applicative zips and a bit shorter:

lm a = foldr (\(x:y:z:_) a -> if y > z && y > x then y:a else a) [] 
             $ filter ((2<) . length) (tails a)

Readability still questionable though :)

Update As suggested by Will Ness, list comprehension also can be used:

lm a = [y | (x:y:z:_) <- tails a, x < y && y > z]
Will Ness
  • 70,110
  • 9
  • 98
  • 181
wonder.mice
  • 7,227
  • 3
  • 36
  • 39
  • The test `length x > 2` is redundant. I've edited this for you. :) this is the cleanest, shortest, nicest solution IMO. (though it's not a fold per se). -- BTW Common Lisp has two "maps" - `mapcar` (like the usual map) and `maplist` - like a `map` on `tails`. But Haskel has list comprehension with quiet pattern-mismatch! – Will Ness Mar 01 '14 at 16:42
  • Nice! But why it doesn't fails with "Irrefutable pattern failed" or "Non-exhaustive patterns"? – wonder.mice Mar 01 '14 at 18:20
  • 1
    exactly! that's the point here. That list comprehension is equivalent to `do {(x:y:z:_)<- tails a; if (xz)then [y] else []}`. [The Report](http://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-470003.14) gives the translation of that as `let { ok (x:y:z:_) = if (xz)then [y] else []; ok _ = fail "error message"} in tails a >>= ok` where `fail _ = []`. The last bit is crucial. List monad's `fail` does not cause errors. It just skips over the mismatches, like `Maybe` monad's `fail` does as well: `let { catMaybes :: [Maybe a] -> [a]; catMaybes xs = [x | Just x <- xs]}`. – Will Ness Mar 01 '14 at 19:15
1

Here is another one that doesn't pattern match on the list at all and uses only fold:

g :: a -> (a -> Bool) -> (a -> Bool) -> [a]
g x p n | p x && n x = [x]
        | otherwise  = []

localMax :: Ord a => [a] -> [a]
localMax l = fr $ const False
  where (_, fr) = foldr worker (const False, \_ -> []) l
        worker x (p,f) = ((> x), \n -> g x p n ++ f (> x))

How does this work?

The basic idea is to use a function in the accumulator, so that we can "pass back" a later element to a previous stage. So we pass back a function of type a -> Bool, that returns True only if the next element is greater than the argument. (this function is called n for nextin the code above). We also have another function of type a -> Bool in the accumulator which returns True if the previous element is less than the passed value (called p for previous). We have a maximum exactly when both of the functions return True. This is what g checks.

Example

*Main> localMax [3,4,2,5,1,7,6,1,9]
[4,5,7]
*Main> localMax [1..10]
[]
*Main> localMax []
[]
bennofs
  • 11,873
  • 1
  • 38
  • 62
1

Here is another one with zipWith

localMax a = [ x | (x,v) <- zip a localMaxInd, v]
where  
   localMaxInd = zipWith (&&) u v
         where 
              u = False : zipWith (>) (tail a) a
              v = zipWith (>) a (tail a) 

test case

> localMax [3,4,2,5,1,7,6,1,9]
[4,5,7]
karakfa
  • 66,216
  • 7
  • 41
  • 56
1

I'd like to @jozefg's answer, that it is possible to express the whole thing using folds. Any recursive operation on lists can be eventually expressed using foldr, but often it's quite ugly.

First let's implement locMax using mapMaybe and zip, very similarly to what @jozefg did::

import Prelude hiding (zip)

isMax :: (Ord a) => ((a, a), a) -> Maybe a
isMax ((x, y), z) | y > z && y > x   = Just y
                  | otherwise        = Nothing

locMax :: (Ord a) => [a] -> [a]
locMax xs@(_:xs'@(_:xs'')) = mapMaybe isMax $ zip (zip xs xs') xs''

Implementing mapMaybe using foldr isn't so difficult:

mapMaybe :: (a -> Maybe b) -> [a] -> [b]
mapMaybe f = foldr (\x xs -> maybe xs (: xs) (f x)) []

Implementing zip is a bit more tricky, since we need to consume two lists at once. What we'll do is that we'll accumulate inside foldr a function of type [b] -> [(a,b)] that'll consume the second list.

The base case is simple. If the first list is empty, the constructed function is const [], so whatever is the second list, the result is also empty.

The folding step takes a value x : a, an accumulated function that converts a sub-list of [b]. And since we're producing a function again, we just take a third argument of type [b]. If there are no bs, the result is an empty list. If there is at least one, we construct a pair and call f on the rest of bs:

zip :: [a] -> [b] -> [(a,b)]
zip = foldr step (const [])
  where
    step _ _ []       = []
    step x f (y : ys) = (x, y) : f ys

You can verify that this implementation has the required properties and works properly also if one or both lists are infinite.

Petr
  • 62,528
  • 13
  • 153
  • 317