Taking from a list until encountering a duplicate

haskell

Solution

One way to go about this is to update a piece of state as you traverse the list, similar to what you'd do in an imperative language. This requires working with the `State` monad, which might take some studying and playing around to get it if it's your first time, but trust me, this is well worth learning. Let's start with imports:

import Control.Monad.State
import Data.Set (Set)
import qualified Data.Set as Set

The state we're going to keep is the `Set` of elements seen up to that point in the list. So first, let's write a pair of simple `State` actions to manage the set of seen elements:

-- Add an element to the context Set
remember :: Ord a => a -> State (Set a) ()
remember a = modify (Set.insert a)

-- Test if the context set contains an element
haveSeen :: Ord a => a -> State (Set a) Bool
haveSeen a = do seen <- get
                return (a `Set.member` seen)

Now we're going to combine these two into an action that checks for duplication:

isDuplicate :: Ord a => a -> State (Set a) Bool
isDuplicate a = do seen <- haveSeen a
                   remember a
                   return seen

You've mentioned the `takeWhile` function. We're going to build our solution along similar lines. This is `takeWhile`'s definition:

-- different name to avoid collision
takeWhile' :: (a -> Bool) -> [a] -> [a]
takeWhile' _ [] =  []
takeWhile' p (a:as)
    | p a =  a : takeWhile p as
    | otherwise =  []

We can modify this function to work with a predicate that has the `Bool` wrapped inside a monad:

takeWhileM :: Monad m => (a -> m Bool) -> [a] -> m [a]
takeWhileM _ [] = return []
takeWhileM p (a:as) = 
    do test <- p a
       if test
       then do rest <- takeWhileM p as
               return (a:rest)
       else return []

But the key difference here is that because the test in `takeWhileM` is monadic, we can use our stateful `isDuplicate` from above. So each time we test an element of the list with `isDuplicate`, we will also record that element in the `Set` that's being threaded through the computation. So now we can write `takeUntilDuplicate` like this:

takeUntilDuplicate :: Ord a => [a] -> [a]
takeUntilDuplicate as = evalState (takeUntilDuplicate' as) Set.empty
    where takeUntilDuplicate' :: Ord a => [a] -> State (Set a) [a]
          takeUntilDuplicate' = takeWhileM (fmap not . isDuplicate)

Example use (with an infinite list argument):

 >>> takeUntilDuplicate (cycle [1..5])
 [1,2,3,4,5]

And what's neat is that several of these pieces of code could be reused for similar problems.

Problem

In Haskell, `takeWhile` allows one to take entries from a (potentially infinite) list until a certain condition does not hold. However, this condition can't depend on the previous entries of the list. How can I `take` entries from a (potentially infinite) list until I encounter the first duplicate as outlined in this example? ``` *Main> takeUntilDuplicate [1,2,3,4,5,1,2,3,4] [1,2,3,4,5] ```

Original source

Related problems