问题
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?
回答1:
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>
回答2:
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.
回答3:
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
.
回答4:
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:
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.
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.
回答5:
"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.
回答6:
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
来源:https://stackoverflow.com/questions/59068318/greaters-function-define