2

I'm working on a simple problem on Programming Praxis: remove all duplicates from a list without changing the order. Assuming the elements are in class Ord, I came up with the following:

import Data.Set (Set)  
import qualified Data.Set as Set

buildsets::Ord a => [a] -> [Set a]
buildsets = scanl (flip Set.insert) Set.empty

nub2::Ord a => [a] -> [a]
nub2 thelist = map fst $ filter (not . uncurry Set.member) (zip thelist (buildsets thelist))

As you can see, the buildsets function gets me most of the way there, but that last step (nub2) of putting everything together looks absolutely horrible. Is there a cleaner way to accomplish this?

9
  • which of the duplicates do you want to keep? Commented Dec 19, 2013 at 21:06
  • @SassaNF, I was going for keeping the first, but it shouldn't matter if the instance declaration follows the rules. Commented Dec 19, 2013 at 21:15
  • g = go Set.empty where go _ [] = []; go s (x:xs) = if Set.member x s then go s xs else x:go (Set.insert x s) xs Commented Dec 19, 2013 at 21:24
  • 1
    If you must use scanl, this works: mapMaybe fst . scanl (\(_, st) e -> if Set.member e st then (Nothing, st) else (Just e, Set.insert e st)) (Nothing, Set.empty). It's also similar to how you might use unfoldr. Commented Dec 19, 2013 at 21:37
  • 2
    g x = concat $ unfoldr go (Set.empty, x) where go (_,[]) = Nothing; go (s,(x:xs)) = Just (if Set.member x s then [] else [x], (Set.insert x s, xs)) Commented Dec 19, 2013 at 21:40

4 Answers 4

3

Since we have to filter the list and we should probably use some set to keep records, we might as well use filterM with the state monad:

import qualified Data.Set as S
import Control.Monad.State.Strict

nub2 :: Ord a => [a] -> [a]
nub2 = (`evalState` S.empty) . filterM go where
    go x = state $ \s -> if S.member x s
        then (False, s)
        else (True, S.insert x s) 

If I wanted to somewhat golf the function, I'd to the following:

import Control.Arrow (&&&)

nub2 = (`evalState` S.empty) . filterM (\x -> state (S.notMember x &&& S.insert x))
Sign up to request clarification or add additional context in comments.

Comments

2

Simple recursion looks ok to me.

> g xs = go xs S.empty where
>   go [] _ = []
>   go (x:xs) a | S.member x a = go xs a
>               | otherwise =  x:go xs (S.insert x a)

1 Comment

You should really explain why this solves the problem, rather than simply saying it does.
0

Based directly on Sassa NF's suggestion, but with a slight type change for cleanliness:

g x = catMaybes $ unfoldr go (Set.empty, x)
  where
    go (_,[]) = Nothing
    go (s,(x:xs)) = Just (if Set.member x s then Nothing else Just x,
                          (Set.insert x s, xs))

Comments

0

Sometimes it really cleans up code to pull out and name subpieces. (In some ways this really is the Haskell way to comment code)

This is wordier that what you did above, but I think it is much easier to understand....

First I start with some definitions:

type Info=([Int], S.Set Int) --This is the remaining and seen items at a point in the list
item=head . fst --The current item
rest=fst --Future items
seen=snd --The items already seen

Then I add two self descriptive helper functions:

itemHasBeenSeen::Info->Bool
itemHasBeenSeen info = item info `S.member` seen info

moveItemToSet::Info->Info
moveItemToSet info = (tail $ rest info, item info `S.insert` seen info)

With this the program becomes:

nub2::[Int]->[Int]
nub2 theList = 
  map item 
  $ filter (not . itemHasBeenSeen) 
  $ takeWhile (not . null . rest) 
  $ iterate moveItemToSet start
    where start = (theList, S.empty)

Reading from bottom to top (just as the data flows), you can easily see what it happening:

  1. start=(theList, S.empty), start with the full list, and an empty set.

  2. iterate moveItemToSet start, repeatedly move the first item of the list into the set, saving each iteration of Info in an array.

  3. takeWhile (not . null . rest)- Stop the iteration when you run out of elements.

  4. filter (not . itemHasBeenSeen)- Remove items that have already been seen.

  5. map item- Throw away the helper values....

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.