packages feed

byte-containers-0.1.0.1: src/Data/Map/Word8.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}

module Data.Map.Word8
  ( Map
  , lookup
  , null
  , size
  , empty
  , singleton
  , union
  , unionWith
  , insert
  , insertWith
  , foldrWithKeys
  , foldl'
  , traverse_
  , toList
  , fromList
  ) where

import Prelude hiding (lookup, null)

import Control.Monad.ST.Run (runSmallArrayST)
import Data.Bits (bit, popCount, testBit, unsafeShiftR, (.&.), (.|.))
import Data.Primitive (SmallArray)
import Data.WideWord (Word256)
import Data.Word (Word8)

import qualified Data.Foldable as F
import qualified Data.Primitive as PM

-- | A map whose keys are 8-bit words.
data Map a
  = Map
      -- Invariant: len(values) = popcnt keys
      {-# UNPACK #-} !Word256
      {-# UNPACK #-} !(SmallArray a)

deriving stock instance (Eq a) => Eq (Map a)
deriving stock instance Functor Map

instance (Show a) => Show (Map a) where
  showsPrec p m = showsPrec p (toList m)

instance (Semigroup a) => Semigroup (Map a) where
  (<>) = unionWith (<>)

instance (Semigroup a) => Monoid (Map a) where
  mempty = empty

singleton :: Word8 -> a -> Map a
singleton !k v =
  Map
    (bit (fromIntegral @Word8 @Int k))
    (runSmallArrayST (PM.newSmallArray 1 v >>= PM.unsafeFreezeSmallArray))

-- | Is the passed map empty?
null :: Map a -> Bool
null m = size m == 0

-- | The number of elements the passed map contains.
size :: Map a -> Int
size (Map keys _) = popCount keys

-- | The empty map.
empty :: Map a
empty = Map 0 mempty

-- | Lookup the value at a key in the map.
lookup :: Word8 -> Map a -> Maybe a
lookup kw (Map keys vals) = case testBit keys k of
  False -> Nothing
  True -> case k of
    0 -> Just (PM.indexSmallArray vals 0)
    _ ->
      let ix = popCount (unsafeShiftR maxBound (256 - k) .&. keys)
       in Just (PM.indexSmallArray vals ix)
 where
  k = fromIntegral @Word8 @Int kw

{- | The expression @'union' t1 t2@ takes the left-biased union
of @t1@ and @t2@. It prefers @t1@ when duplicate keys are
encountered (i.e. @'union' == 'unionWith' const@).
-}
union :: Map a -> Map a -> Map a
union !ma@(Map ksA vsA) !mb@(Map ksB vsB)
  | ksA == 0 = mb
  | ksB == 0 = ma
  | otherwise = Map ks $ runSmallArrayST do
      let sz = popCount ks
      dst <- PM.newSmallArray sz =<< PM.indexSmallArrayM vsA 0
      foldlZipBits256
        ( \(!ix, !ixA, !ixB) a b -> case a of
            True -> do
              PM.writeSmallArray dst ix =<< PM.indexSmallArrayM vsA ixA
              pure (ix + 1, ixA + 1, if b then ixB + 1 else ixB)
            False -> case b of
              True -> do
                PM.writeSmallArray dst ix =<< PM.indexSmallArrayM vsB ixB
                pure (ix + 1, ixA, ixB + 1)
              False -> pure (ix, ixA, ixB)
        )
        (0, 0, 0)
        ksA
        ksB
      PM.unsafeFreezeSmallArray dst
 where
  ks = ksA .|. ksB

-- | Union with a combining function.
unionWith :: (a -> a -> a) -> Map a -> Map a -> Map a
unionWith g !ma@(Map ksA vsA) !mb@(Map ksB vsB)
  | ksA == 0 = mb
  | ksB == 0 = ma
  | otherwise = Map ks $ runSmallArrayST do
      let sz = popCount ks
      dst <- PM.newSmallArray sz =<< PM.indexSmallArrayM vsA 0
      foldlZipBits256
        ( \(!ix, !ixA, !ixB) a b -> case a of
            True -> case b of
              True -> do
                a' <- PM.indexSmallArrayM vsA ixA
                b' <- PM.indexSmallArrayM vsB ixB
                let !c = g a' b'
                PM.writeSmallArray dst ix c
                pure (ix + 1, ixA + 1, ixB + 1)
              False -> do
                PM.writeSmallArray dst ix =<< PM.indexSmallArrayM vsA ixA
                pure (ix + 1, ixA + 1, ixB)
            False -> case b of
              True -> do
                PM.writeSmallArray dst ix =<< PM.indexSmallArrayM vsB ixB
                pure (ix + 1, ixA, ixB + 1)
              False -> pure (ix, ixA, ixB)
        )
        (0, 0, 0)
        ksA
        ksB
      PM.unsafeFreezeSmallArray dst
 where
  ks = ksA .|. ksB

insert :: Word8 -> a -> Map a -> Map a
insert = insertWith const

{- | Insert with a function, combining new value and old value.
@'insertWith' f key value mp@ will insert the pair @(key, value)@ into @mp@
if @key@ does not exist in the map.
If the key does exist, the function will insert the pair
@(key, f new_value old_value)@.
-}
insertWith :: (a -> a -> a) -> Word8 -> a -> Map a -> Map a
insertWith f k v m = unionWith f (singleton k v) m

-- Internal function. This is strict in the accumulator.
foldlZipBits256 ::
  (Monad m) =>
  (a -> Bool -> Bool -> m a) ->
  a ->
  Word256 ->
  Word256 ->
  m ()
foldlZipBits256 g !a0 !x !y = go 0 a0
 where
  go !ix !a = case ix of
    256 -> pure ()
    _ -> do
      let xval = testBit x ix
      let yval = testBit y ix
      a' <- g a xval yval
      go (ix + 1) a'

_foldrBits256 :: (Word8 -> b -> b) -> b -> Word256 -> b
_foldrBits256 g b0 w = go 0
 where
  go ix = case ix of
    256 -> b0
    _ -> case testBit w ix of
      True -> g (fromIntegral @Int @Word8 ix) (go (ix + 1))
      False -> go (ix + 1)

foldrWithKeys :: (Word8 -> a -> b -> b) -> b -> Map a -> b
foldrWithKeys g b0 (Map ks vs) = go 0 0
 where
  go !ix !ixVal = case ix of
    256 -> b0
    _ -> case testBit ks ix of
      True ->
        g
          (fromIntegral @Int @Word8 ix)
          (PM.indexSmallArray vs ixVal)
          (go (ix + 1) (ixVal + 1))
      False -> go (ix + 1) ixVal

foldl' :: (b -> a -> b) -> b -> Map a -> b
{-# INLINE foldl' #-}
foldl' f b0 (Map _ vs) = F.foldl' f b0 vs

traverse_ :: (Applicative m) => (a -> m b) -> Map a -> m ()
{-# INLINE traverse_ #-}
traverse_ f (Map _ vs) = F.traverse_ f vs

toList :: Map a -> [(Word8, a)]
toList = foldrWithKeys (\k v b -> (k, v) : b) []

fromList :: [(Word8, a)] -> Map a
fromList = F.foldl' (\acc (k, v) -> union acc (singleton k v)) empty