packages feed

openapi-hs-5.0.0: src/Data/HashSet/InsOrd/Compat.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeFamilies #-}

-- | Vendored from @insert-ordered-containers-0.3.0@.
--
-- Copyright (c) 2015-2026 Oleg Grenrus
-- Licensed under BSD-3-Clause; see
-- @LICENSES/insert-ordered-containers-BSD-3-Clause.txt@.
--
-- 'InsOrdHashSet' is like 'HashSet', but it folds in insertion order.
-- This module interface mimics "Data.HashSet", with some additions.
module Data.HashSet.InsOrd.Compat
  ( InsOrdHashSet,

    -- * Construction
    empty,
    singleton,

    -- * Basic interface
    null,
    size,
    member,
    insert,
    delete,

    -- * Combine
    union,

    -- * Transformations
    map,

    -- ** Unordered

    -- * Difference and intersection
    difference,
    intersection,

    -- * Folds

    -- ** Unordered

    -- * Filter
    filter,

    -- * Conversions
    toList,
    fromList,
    toHashSet,
    fromHashSet,

    -- * Lenses
    hashSet,

    -- * Debugging
    valid,
  )
where

import Control.Arrow (first)
import Control.DeepSeq (NFData (..))
import Control.DeepSeq qualified as NF
import Control.Lens
  ( At (..),
    Contains (..),
    Index,
    Iso',
    IxValue,
    Ixed (..),
    iso,
    (<&>),
  )
import Control.Lens qualified as Lens
import Control.Monad.Trans.State.Strict (State, runState, state)
import Data.Aeson qualified as A
import Data.Data (Data)
import Data.Foldable (Foldable (foldMap), all)
import Data.Foldable qualified
import Data.HashMap.InsOrd.Compat.Internal
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Data.HashSet (HashSet)
import Data.HashSet qualified as HashSet
import Data.Hashable (Hashable (..))
import Data.List (nub, sortBy)
import Data.Monoid (Monoid (..))
import Data.Ord (comparing)
import Data.Semigroup (Semigroup (..))
import Data.Traversable (Traversable (traverse))
import GHC.Exts qualified as Exts
import Text.ParserCombinators.ReadPrec (prec)
import Text.Read
  ( Lexeme (..),
    Read (..),
    lexP,
    parens,
    readListPrecDefault,
  )
import Text.Show (Show (..), showParen, showString)
import Prelude
  ( Bool,
    Eq ((==)),
    Int,
    Maybe (..),
    const,
    flip,
    fmap,
    fst,
    maybe,
    otherwise,
    return,
    snd,
    ($),
    (&&),
    (+),
    (.),
    (<),
    (<$),
    (<$>),
    (>),
    (>=),
    (||),
  )

-------------------------------------------------------------------------------
-- InsOrdHashSet
-------------------------------------------------------------------------------

-- | 'HashSet' which tries its best to remember insertion order of elements.
data InsOrdHashSet k = InsOrdHashSet
  { _getIndex :: !Int,
    getInsOrdHashSet :: !(HashMap k Int)
  }
  deriving (Data)

-- | @since 0.2.5
instance (NFData k) => NFData (InsOrdHashSet k) where
  rnf (InsOrdHashSet _ hs) = rnf hs

-- | @since 0.2.5
instance NF.NFData1 InsOrdHashSet where
  liftRnf rnf1 (InsOrdHashSet _ m) = NF.liftRnf2 rnf1 rnf m

instance (Eq k) => Eq (InsOrdHashSet k) where
  InsOrdHashSet _ a == InsOrdHashSet _ b = a == b

instance (Show k) => Show (InsOrdHashSet k) where
  showsPrec d m =
    showParen (d > 10) $
      showString "fromList " . showsPrec 11 (toList m)

instance (Eq k, Hashable k, Read k) => Read (InsOrdHashSet k) where
  readPrec = parens $ prec 10 $ do
    Ident "fromList" <- lexP
    xs <- readPrec
    return (fromList xs)

  readListPrec = readListPrecDefault

instance (Eq k, Hashable k) => Semigroup (InsOrdHashSet k) where
  (<>) = union

instance (Eq k, Hashable k) => Monoid (InsOrdHashSet k) where
  mempty = empty

instance Foldable InsOrdHashSet where
  -- in newer base only
  -- length = length . getInsOrdHashSet
  foldMap f = foldMap f . toList

  null = null
  toList = toList
  length = size

-- | @'hashWithSalt' salt . 'toHashSet' = 'hashWithSalt' salt@.
instance (Hashable k) => Hashable (InsOrdHashSet k) where
  hashWithSalt salt (InsOrdHashSet _ m) =
    hashWithSalt salt m

instance (Eq k, Hashable k) => Exts.IsList (InsOrdHashSet k) where
  type Item (InsOrdHashSet k) = k
  fromList = fromList
  toList = toList

-------------------------------------------------------------------------------
-- Aeson
-------------------------------------------------------------------------------

instance (A.ToJSON a) => A.ToJSON (InsOrdHashSet a) where
  toJSON = A.toJSON . toList
  toEncoding = A.toEncoding . toList

instance (Eq a, Hashable a, A.FromJSON a) => A.FromJSON (InsOrdHashSet a) where
  parseJSON v = fromList <$> A.parseJSON v

-------------------------------------------------------------------------------
-- Lens
-------------------------------------------------------------------------------

type instance Index (InsOrdHashSet a) = a

type instance IxValue (InsOrdHashSet a) = ()

instance (Eq k, Hashable k) => Ixed (InsOrdHashSet k) where
  ix k f (InsOrdHashSet i m) = InsOrdHashSet i <$> ix k (\j -> j <$ f ()) m
  {-# INLINE ix #-}

instance (Eq k, Hashable k) => At (InsOrdHashSet k) where
  at k f m =
    f mv <&> \r -> case r of
      Nothing -> maybe m (const (delete k m)) mv
      Just () -> insert k m
    where
      mv = if member k m then Just () else Nothing
  {-# INLINE at #-}

instance (Eq a, Hashable a) => Contains (InsOrdHashSet a) where
  contains k f s =
    f (member k s) <&> \b ->
      if b then insert k s else delete k s
  {-# INLINE contains #-}

-- | This is a slight lie, as roundtrip doesn't preserve ordering.
hashSet :: Iso' (InsOrdHashSet a) (HashSet a)
hashSet = iso toHashSet fromHashSet

-------------------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------------------

empty :: InsOrdHashSet k
empty = InsOrdHashSet 0 HashMap.empty
{-# INLINEABLE empty #-}

singleton :: (Hashable k) => k -> InsOrdHashSet k
singleton k = InsOrdHashSet 1 (HashMap.singleton k 0)
{-# INLINEABLE singleton #-}

-------------------------------------------------------------------------------
-- Basic interface
-------------------------------------------------------------------------------

null :: InsOrdHashSet k -> Bool
null = HashMap.null . getInsOrdHashSet
{-# INLINEABLE null #-}

size :: InsOrdHashSet k -> Int
size = HashMap.size . getInsOrdHashSet
{-# INLINEABLE size #-}

member :: (Eq k, Hashable k) => k -> InsOrdHashSet k -> Bool
member k = HashMap.member k . getInsOrdHashSet
{-# INLINEABLE member #-}

insert :: (Eq k, Hashable k) => k -> InsOrdHashSet k -> InsOrdHashSet k
insert k (InsOrdHashSet i m) = InsOrdHashSet (i + 1) (HashMap.insert k i m)

delete :: (Eq k, Hashable k) => k -> InsOrdHashSet k -> InsOrdHashSet k
delete k (InsOrdHashSet i m) = InsOrdHashSet i (HashMap.delete k m)

-------------------------------------------------------------------------------
-- Combine
-------------------------------------------------------------------------------

union ::
  (Eq k, Hashable k) =>
  InsOrdHashSet k -> InsOrdHashSet k -> InsOrdHashSet k
union (InsOrdHashSet i a) (InsOrdHashSet j b) =
  mk $ HashMap.union a b'
  where
    mk
      | i >= 0xfffff || j >= 0xfffff = fromHashMapInt
      | otherwise = InsOrdHashSet (i + j)

    b' = fmap (\k -> k + i + 1) b

-------------------------------------------------------------------------------
-- Transformations
-------------------------------------------------------------------------------

map :: (Hashable b, Eq b) => (a -> b) -> InsOrdHashSet a -> InsOrdHashSet b
map f (InsOrdHashSet i m) =
  InsOrdHashSet i $
    HashMap.fromList . fmap (first f) . HashMap.toList $
      m

-------------------------------------------------------------------------------
-- Difference and intersection
-------------------------------------------------------------------------------

difference :: (Eq a, Hashable a) => InsOrdHashSet a -> InsOrdHashSet a -> InsOrdHashSet a
difference (InsOrdHashSet i a) (InsOrdHashSet _ b) =
  InsOrdHashSet i $ HashMap.difference a b

intersection :: (Eq a, Hashable a) => InsOrdHashSet a -> InsOrdHashSet a -> InsOrdHashSet a
intersection (InsOrdHashSet i a) (InsOrdHashSet _ b) =
  InsOrdHashSet i $ HashMap.intersection a b

-------------------------------------------------------------------------------
-- Filter
-------------------------------------------------------------------------------

filter :: (a -> Bool) -> InsOrdHashSet a -> InsOrdHashSet a
filter p (InsOrdHashSet i m) =
  InsOrdHashSet i $
    HashMap.filterWithKey (\k _ -> p k) m

-------------------------------------------------------------------------------
-- Conversions
-------------------------------------------------------------------------------

fromList :: (Eq k, Hashable k) => [k] -> InsOrdHashSet k
fromList = mk . flip runState 0 . traverse newInt
  where
    mk (m, i) = InsOrdHashSet i (HashMap.fromList m)

toList :: InsOrdHashSet k -> [k]
toList =
  fmap fst
    . sortBy (comparing snd)
    . HashMap.toList
    . getInsOrdHashSet

fromHashSet :: HashSet k -> InsOrdHashSet k
fromHashSet = mk . flip runState 0 . traverse (const newInt') . HashSet.toMap
  where
    mk (m, i) = InsOrdHashSet i m

toHashSet :: InsOrdHashSet k -> HashSet k
toHashSet (InsOrdHashSet _ m) =
  HashMap.keysSet m

-------------------------------------------------------------------------------
-- Internal
-------------------------------------------------------------------------------

fromHashMapInt :: HashMap k Int -> InsOrdHashSet k
fromHashMapInt = mk . flip runState 0 . retractSortedAp . traverse f
  where
    mk (m, i) = InsOrdHashSet i m
    f i = liftSortedAp i newInt'

newInt :: a -> State Int (a, Int)
newInt a = state $ \s -> ((a, s), s + 1)

newInt' :: State Int Int
newInt' = state $ \s -> (s, s + 1)

-------------------------------------------------------------------------------
-- Valid
-------------------------------------------------------------------------------

-- | Test if the internal map structure is valid.
valid :: InsOrdHashSet a -> Bool
valid (InsOrdHashSet i m) = indexesDistinct && indexesSmaller
  where
    indexes :: [Int]
    indexes = HashMap.elems m

    indexesDistinct = indexes == nub indexes
    indexesSmaller = all (< i) indexes