A library-based solution:
We can use that style of Haskell programming where all looping and recursion activities are pushed out of user code and into suitable library functions. Said library functions are often optimized in ways that are way beyond the skills of a Haskell beginner.
A way to decompose the problem into two passes goes like this:
- produce a second list that is parallel to the input list, but with duplicate elements suitably marked
- eliminate elements marked as duplicates from that second list
For the first step, duplicate elements don't need a value at all, so we can use [Maybe a] as the type of the second list. So we need a function of type:
pass1 :: Eq a => [a] -> [Maybe a]
Function pass1 is an example of stateful list traversal where the state is the list (or set) of distinct elements seen so far. For this sort of problem, the library provides the mapAccumL :: (s -> a -> (s, b)) -> s -> [a] -> (s, [b]) function.
Here the mapAccumL function requires, besides the initial state and the input list, a step function argument, of type s -> a -> (s, Maybe a).
If the current element x is not a duplicate, the output of the step function is Just x and x gets added to the current state. If x is a duplicate, the output of the step function is Nothing, and the state is passed unchanged.
Testing under the ghci interpreter:
$ ghci GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help λ> λ> stepFn s x = if (elem x s) then (s, Nothing) else (x:s, Just x) λ> λ> import Data.List(mapAccumL) λ> λ> pass1 xs = mapAccumL stepFn [] xs λ> λ> xs2 = snd $ pass1 "abacrba" λ> xs2 [Just 'a', Just 'b', Nothing, Just 'c', Just 'r', Nothing, Nothing] λ>
Writing a pass2 function is even easier. To filter out Nothing non-values, we could use:
import Data.Maybe( fromJust, isJust) pass2 = (map fromJust) . (filter isJust)
but why bother at all ? - as this is precisely what the catMaybes library function does.
λ> λ> import Data.Maybe(catMaybes) λ> λ> catMaybes xs2 "abcr" λ>
Putting it all together:
Overall, the source code can be written as:
import Data.Maybe(catMaybes) import Data.List(mapAccumL) uniques :: (Eq a) => [a] -> [a] uniques = let stepFn s x = if (elem x s) then (s, Nothing) else (x:s, Just x) in catMaybes . snd . mapAccumL stepFn []
This code is reasonably compatible with infinite lists, something occasionally referred to as being “laziness-friendly”:
λ> λ> take 5 $ uniques $ "abacrba" ++ (cycle "abcrf") "abcrf" λ>
Efficiency note: If we anticipate that it is possible to find many distinct elements in the input list and we can have an Ord a instance, the state can be implemented as a Set object rather than a plain list, this without having to alter the overall structure of the solution.
hasis also standard; it's justflip elem.has xs = (`elem` xs).elemas infix?elemhas typeEq a => a -> [a] -> Boolso using it as an infix operation section makesxsthe second argument.(`elem` xs)is desugared to(\x -> elem x xs)which is what we want here!