2

I would like to define a greaters function, which selects from a list items that are larger than the one before it.

For instance:

greaters [1,3,2,4,3,4,5] == [3,4,4,5]
greaters [5,10,6,11,7,12] == [10,11,12]

The definition I came up with is this :

greaters :: Ord a => [a] -> [a]

Things I tried so far:

greaters (x:xs) = group [ d | d <- xs, x < xs ]

Any tips?

Beszteri
  • 185
  • 1
  • 8
  • 1
    This is quite an interesting problem. I don't have time to write an answer but, as a hint, my first thought is to use `zip` to pair up elements of the list with their predecessors, and work from there. Doubtless there are other approaches though. – Robin Zigmond Nov 27 '19 at 10:45
  • 1
    Just roll your own recursive function, that should not be that hard. Is that a homework assignment? – typetetris Nov 27 '19 at 10:50
  • 1
    `zip` the list with its `tail` to get a list of pairs like `[(elem, prev_elem)]`, then filter by the condition and lastly retrive the first elem of each pair. Be carefull! the tail of a list is not a total function – lsmor Nov 27 '19 at 11:55
  • This looks quite similar to [SO question 58687161](https://stackoverflow.com/questions/58687161/how-to-filter-list-elements-in-haskell-based-on-previous-value-in-the-list/58691485#58691485). Title of question: How to filter list elements in Haskell based on previous value in the list? – jpmarinier Nov 27 '19 at 12:59
  • 1
    @lsmor `zip [] (tail [])` works. :) of course you're right, it's cleaner to write `zip xs (drop 1 xs)`. – Will Ness Nov 27 '19 at 15:07

6 Answers6

4

We can derive a foldr-based solution by a series of re-writes starting from the hand-rolled recursive solution in the accepted answer:

greaters :: Ord a => [a] -> [a]
greaters [] = []
greaters (x:xs) = go x xs          -- let's re-write this clause
  where
    go _ [] = []
    go last (act:xs)
      | last < act  =  act : go act xs
      | otherwise   =        go act xs

greaters (x:xs) = go xs x          -- swap the arguments
  where
    go [] _ = []
    go (act:xs) last
      | last < act  =  act : go xs act 
      | otherwise   =        go xs act 

greaters (x:xs) = foldr g z xs x   -- go ==> foldr g z
  where
    foldr g z [] _ = []
    foldr g z (act:xs) last
      | last < act  =  act : foldr g z xs act 
      | otherwise   =        foldr g z xs act 

greaters (x:xs) = foldr g z xs x
  where                          -- simplify according to
    z _ = []                     --   foldr's definition
    g act (foldr g z xs) last 
      | last < act  =  act : foldr g z xs act 
      | otherwise   =        foldr g z xs act 

Thus, with one last re-write of foldr g z xs ==> r,

greaters (x:xs) = foldr g z xs x
  where
    z = const []
    g act r last
      | last < act  =  act : r act 
      | otherwise   =        r act 

The extra parameter serves as a state being passed forward as we go along the input list, the state being the previous element; thus avoiding the construction by zip of the shifted-pairs list serving the same purpose.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • @dfeuer thanks for the edit, w/ guards it's much cleaner of course. would you mind terribly if I flattened and sparsed it up a bit and aligned vertically? it's much easier to "see" the code that way, I think. if you much prefer your version we'd revert. – Will Ness Nov 27 '19 at 20:07
  • I'll offer a compromise: make it more horizontal but reduce all tabs to two spaces. Also, have a look at my answer. – dfeuer Nov 27 '19 at 20:12
  • Very nice deduction! – typetetris Nov 27 '19 at 22:19
  • @typetetris it's a bit tricky (not to say trickster-y) because some intermediate steps are of course not exactly valid Haskell and it's all very handwavy and approximate but could be made more rigorous, perhaps. :) – Will Ness Nov 28 '19 at 08:20
  • That's cool but then still requires a check for `[]` i suppose. – Redu Nov 28 '19 at 20:13
  • @Redu have you missed it at the top of the definition? the re-writes are applied only to the second clause. I've added a clarifying comment. – Will Ness Nov 29 '19 at 08:05
2

I would start from here:

greaters :: Ord a => [a] -> [a]
greaters [] = []
greaters (x:xs) = greatersImpl x xs
    where
        greatersImpl last [] = <fill this out>
        greatersImpl last (x:xs) = <fill this out>
typetetris
  • 4,586
  • 16
  • 31
2

The following functions are everything you’d need for one possible solution :)

zip :: [a] -> [b] -> [(a, b)]
drop 1 :: [a] -> [a]
filter :: (a -> Bool) -> [a] -> [a]
(<) :: Ord a => a -> a -> Bool
uncurry :: (a -> b -> c) -> (a, b) -> c
map :: (a -> b) -> [a] -> [b]
snd :: (a, b) -> b

Note: drop 1 can be used when you’d prefer a “safe” version of tail.

parkerbrads
  • 116
  • 1
  • 4
1

If you like over-generalization like me, you can use the witherable package.

{-# language ScopedTypeVariables #-}

import Control.Monad.State.Lazy
import Data.Witherable

{-
class (Traversable t, Filterable t) => Witherable t where
  -- `wither` is an effectful version of mapMaybe. 
  wither :: Applicative f => (a -> f (Maybe b)) -> t a -> f (t b)
-}

greaters
  :: forall t a. (Ord a, Witherable t)
  => t a -> t a
greaters xs = evalState (wither go xs) Nothing
  where
    go :: a -> State (Maybe a) (Maybe a)
    go curr = do
      st <- get
      put (Just curr)
      pure $ case st of
        Nothing -> Nothing
        Just prev ->
          if curr > prev
            then Just curr
            else Nothing

The state is the previous element, if there is one. Everything is about as lazy as it can be. In particular:

  1. If the container is a Haskell list, then it can be an infinite one and everything will still work. The beginning of the list can be produced without withering the rest.

  2. If the container extends infinitely to the left (e.g., an infinite snoc list), then everything will still work. How can that be? We only need to know what was in the previous element to work out the state for the current element.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
0

"Roll your own recursive function" is certainly an option here, but it can also be accomplished with a fold. filter can't do it because we need some sort of state being passed, but fold can nicely accumulate the result while keeping that state at the same time.

Of course the key idea is that we keep track of last element add the next one to the result set if it's greater than the last one.

greaters :: [Int] -> [Int]
greaters [] = []
greaters (h:t) = reverse . snd $ foldl (\(a, r) x -> (x, if x > a then x:r else r)) (h, []) t

I'd really love to eta-reduce it but since we're dropping the first element and seeding the accumulator with it it kinda becomes awkward with the empty list; still, this is effectively an one-liner.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • 1
    why shouldn't it work for infinite lists? nothing inherently demands `take 3 . greaters $ [1..]` to diverge. – Will Ness Nov 27 '19 at 15:27
  • 1
    `foldr` can do this. the hand-rolled recursion in the accepted answer fits the `foldr` pattern, just needs [some arguments flipping](https://stackoverflow.com/a/59075309/849891) in the worker. – Will Ness Nov 27 '19 at 17:17
  • Yeah, this seems much more like a `foldr` problem than a `foldl` one. – dfeuer Nov 27 '19 at 18:12
0

So i have come up with a foldr solution. It should be similar to what @Will Ness has demonstrated but not quite i suppose as we don't need a separate empty list check in this one.

The thing is, while folding we need to encapsulate the previous element and also the state (the result) in a function type. So in the go helper function f is the state (the result) c is the current element of interest and p is the previous one (next since we are folding right). While folding from right to left we are nesting up these functions only to run it by applyying the head of the input list to it.

go :: Ord a => a -> (a -> [a]) -> (a -> [a])
go c f = \p -> let r = f c 
               in if c > p then c:r else r

greaters :: Ord a => [a] -> [a]
greaters = foldr go (const []) <*> head

*Main> greaters [1,3,2,4,3,4,5]
[3,4,4,5]
*Main> greaters [5,10,6,11,7,12]
[10,11,12]
*Main> greaters [651,151,1651,21,651,1231,4,1,16,135,87]
[1651,651,1231,16,135]
*Main> greaters [1]
[]
*Main> greaters []
[]

As per rightful comments of @Will Ness here is a modified slightly more general code which hopefully doesn't break suddenly when the comparison changes. Note that const [] :: b -> [a] is the initial function and [] is the terminator applied to the result of foldr. We don't need Maybe since [] can easily do the job of Nothing here.

gs :: Ord a => [a] -> [a]
gs xs = foldr go (const []) xs $ []
        where
        go :: Ord a => a -> ([a] -> [a]) -> ([a] -> [a])
        go c f = \ps -> let r = f [c]
                        in case ps of
                           []  -> r
                           [p] -> if c > p then c:r else r
Redu
  • 25,060
  • 6
  • 56
  • 76
  • 1
    I'd much prefer it as `greaters = foldr g (const []) <*> head where g c r p | c>p = c:r c | otherwise = r c`. (personally, I always use `r` there, as a mnemonic for "recursive result"; that it is used as a function is self-evident, I don't need it to be called `f` to remind me of that; I need much more the reminder of the role it plays in the overall `foldr` scheme). but I doubt it'd be considered good style, it is too clever for its own good, with the head, and the const, etc.. too fragile. just imagine requirements change from `<` to `<=`, and suddenly it breaks. – Will Ness Nov 30 '19 at 18:12
  • there's no reason for `go` to be globally visible. also the name, `go` is usually used for recursive functions. --- the standard way to avoid the `[]` clause is with the `Maybe`, like in dfeuer's answer. – Will Ness Nov 30 '19 at 18:15
  • @WillNess So yes. i have to agree that head is hackish here and i hope to fix that. I think, as you had already mentioned somewhere, before, a **stateful fold** could be quite handy to solve problems where `foldl1` falls in short. I mean a `foldS :: Foldable t => (a -> a -> b) -> t a -> b` could be reasonable in general. As per your second comment; `go` being globally visible, that's just a matchstick code what i have displayed... `go` can easily be moved down under a where clause within greaters. I would really aprreciate if you could drop me an email so that i could consult things privately. – Redu Nov 30 '19 at 19:54