packages feed

swarm-0.6.0.0: src/swarm-util/Swarm/Util/OccurrenceEncoder.hs

-- |
-- SPDX-License-Identifier: BSD-3-Clause
-- Description: Compress representation of traversable
--
-- Useful for compressing the representation of a
-- structure that has many repeating elements
-- for transmission (e.g. over the network).
module Swarm.Util.OccurrenceEncoder (
  runEncoder,
) where

import Control.Monad.Trans.State
import Data.List (sortOn)
import Data.Map (Map)
import Data.Map qualified as M

type OccurrenceEncoder a = State (Encoder a)

newtype Encoder a = Encoder (Map a Int)

-- |
-- Given a data structure that may have many repeating "complex" elements,
-- will store the "complex" element representation
-- in an array so that the structure's elements can be replaced
-- with simple indices into that array.
--
-- The first encountered element is assigned index 0, and the next
-- novel element encountered gets index 1, and so on.
runEncoder ::
  (Traversable t, Ord b) =>
  t b ->
  (t Int, [b])
runEncoder structure =
  getIndices <$> runState (mapM encodeOccurrence structure) emptyEncoder

emptyEncoder :: Ord a => Encoder a
emptyEncoder = Encoder mempty

-- | Map indices are guaranteed to be contiguous
-- from @[0..N]@, so we may convert to a list
-- with no loss of information.
getIndices :: Encoder a -> [a]
getIndices (Encoder m) = map fst $ sortOn snd $ M.toList m

-- | Translate each the first occurrence in the structure
-- to a new integer as it is encountered.
-- Subsequent encounters re-use the allocated integer.
encodeOccurrence :: Ord a => a -> OccurrenceEncoder a Int
encodeOccurrence c = do
  Encoder currentMap <- get
  maybe (cacheNewIndex currentMap) return $
    M.lookup c currentMap
 where
  cacheNewIndex currentMap = do
    put $ Encoder $ M.insert c newIdx currentMap
    return newIdx
   where
    newIdx = M.size currentMap