{-# LANGUAGE CPP  #-}
{-# LANGUAGE Safe #-}

{- |
Copyright:  (c) 2016 Stephen Diehl
            (c) 2016-2018 Serokell
            (c) 2018-2021 Kowainik
SPDX-License-Identifier: MIT
Maintainer:  Kowainik <xrom.xkov@gmail.com>
Stability:   Stable
Portability: Portable

Functions to remove duplicates from a list.

 = Performance
 To check the performance there was done a bunch of benchmarks.
 Benchmarks were made on lists of 'Prelude.Int's and 'Data.Text.Text's.
 There were two types of list to use:

 * Lists which consist of many different elements

 * Lists which consist of many same elements


 Here are some recommendations for usage of particular functions based on benchmarking results.

 * 'hashNub' is faster than 'ordNub' when there're not so many different values in the list.

 * 'hashNub' is the fastest with 'Data.Text.Text'.

 * 'intNub' is faster when you work with lists of 'Int's.

 * 'intNubOn' is fast with the lists of type that can have fixed number representations.

 * 'sortNub' has better performance than 'ordNub' but should be used when sorting is also needed.

 * 'unstableNub' has better performance than 'hashNub' but doesn't save the original order.
-}

module Relude.Nub
    ( hashNub
    , ordNub

#if __GLASGOW_HASKELL__ > 804
    , ordNubOn
    , intNub
    , intNubOn
#endif

    , sortNub
    , unstableNub
    ) where

import Data.Eq (Eq)
import Data.Hashable (Hashable)
import Data.HashSet as HashSet
import Data.Ord (Ord)
import Prelude (Int, (.))

import qualified Data.Set as Set
#if __GLASGOW_HASKELL__ > 804
import qualified Data.Containers.ListUtils as Containers
#endif


-- $setup
-- >>> import Prelude (div, fromEnum)

{- | Removes duplicate elements from a list, keeping only the first occurance of
the element.

Like 'Prelude.nub' but runs in \( O(n \log n) \)  time and requires 'Ord'.

>>> ordNub [3, 3, 3, 2, 2, -1, 1]
[3,2,-1,1]

-}
ordNub :: forall a . (Ord a) => [a] -> [a]
#if __GLASGOW_HASKELL__ > 804
ordNub :: forall a. Ord a => [a] -> [a]
ordNub = [a] -> [a]
forall a. Ord a => [a] -> [a]
Containers.nubOrd
{-# INLINE ordNub #-}
#else
ordNub = go Set.empty
  where
    go :: Set.Set a -> [a] -> [a]
    go _ []     = []
    go s (x:xs) =
      if x `Set.member` s
      then go s xs
      else x : go (Set.insert x s) xs
{-# INLINEABLE ordNub #-}
#endif


#if __GLASGOW_HASKELL__ > 804
{- | Similar to 'ordNub' but performs nub through the mapped list on the given
function.

>>> ordNubOn (`div` 10) [3, 3, 3, 13, 2, 22, -1, 1, 66]
[3,13,22,-1,66]

@since 1.0.0.0
-}
ordNubOn :: forall b a . (Ord b) => (a -> b) -> [a] -> [a]
ordNubOn :: forall b a. Ord b => (a -> b) -> [a] -> [a]
ordNubOn = (a -> b) -> [a] -> [a]
forall b a. Ord b => (a -> b) -> [a] -> [a]
Containers.nubOrdOn
{-# INLINE ordNubOn #-}
#endif

{- | Like 'Prelude.nub' but runs in \( O(n \log_{16} n) \)  time and requires 'Hashable'.

>>> hashNub [3, 3, 3, 2, 2, -1, 1]
[3,2,-1,1]

-}
hashNub :: forall a . (Eq a, Hashable a) => [a] -> [a]
hashNub :: forall a. (Eq a, Hashable a) => [a] -> [a]
hashNub = HashSet a -> [a] -> [a]
go HashSet a
forall a. HashSet a
HashSet.empty
  where
    go :: HashSet.HashSet a -> [a] -> [a]
    go :: HashSet a -> [a] -> [a]
go HashSet a
_ []     = []
    go HashSet a
s (a
x:[a]
xs) =
      if a
x a -> HashSet a -> Bool
forall a. (Eq a, Hashable a) => a -> HashSet a -> Bool
`HashSet.member` HashSet a
s
      then HashSet a -> [a] -> [a]
go HashSet a
s [a]
xs
      else a
x a -> [a] -> [a]
forall a. a -> [a] -> [a]
: HashSet a -> [a] -> [a]
go (a -> HashSet a -> HashSet a
forall a. (Eq a, Hashable a) => a -> HashSet a -> HashSet a
HashSet.insert a
x HashSet a
s) [a]
xs
{-# INLINEABLE hashNub #-}

{- | Like 'ordNub' runs in \( O(n \log n) \)  but also sorts a list.

>>> sortNub [3, 3, 3, 2, 2, -1, 1]
[-1,1,2,3]

-}
sortNub :: (Ord a) => [a] -> [a]
sortNub :: forall a. Ord a => [a] -> [a]
sortNub = Set a -> [a]
forall a. Set a -> [a]
Set.toList (Set a -> [a]) -> ([a] -> Set a) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> Set a
forall a. Ord a => [a] -> Set a
Set.fromList
{-# INLINE sortNub #-}

{- | Like 'hashNub' runs in \( O(n \log_{16} n) \) but has better performance; it doesn't save the order.

>>> unstableNub [3, 3, 3, 2, 2, -1, 1]
[1,2,3,-1]

-}
unstableNub :: (Eq a, Hashable a) => [a] -> [a]
unstableNub :: forall a. (Eq a, Hashable a) => [a] -> [a]
unstableNub = HashSet a -> [a]
forall a. HashSet a -> [a]
HashSet.toList (HashSet a -> [a]) -> ([a] -> HashSet a) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> HashSet a
forall a. (Eq a, Hashable a) => [a] -> HashSet a
HashSet.fromList
{-# INLINE unstableNub #-}


#if __GLASGOW_HASKELL__ > 804

{- | Removes duplicate elements from a list, keeping only the first occurance of
the element.

Like 'Prelude.nub' but runs in \( O(n \min\(n, int_bits\)) \)  time and requires 'Ord'.

>>> intNub [3, 3, 3, 2, 2, -1, 1]
[3,2,-1,1]

@since 1.0.0.0
-}
intNub :: [Int] -> [Int]
intNub :: [Int] -> [Int]
intNub = [Int] -> [Int]
Containers.nubInt

{-# INLINE intNub #-}

{- | Similar to 'intNub' but works on lists of any types by performing "nubbing" through 'Int's.

>>> intNubOn fromEnum "ababbbcdaffee"
"abcdfe"

@since 1.0.0.0
-}
intNubOn :: (a -> Int) -> [a] -> [a]
intNubOn :: forall a. (a -> Int) -> [a] -> [a]
intNubOn = (a -> Int) -> [a] -> [a]
forall a. (a -> Int) -> [a] -> [a]
Containers.nubIntOn
{-# INLINE intNubOn #-}

#endif