packages feed

moonlight-core-0.1.0.0: src-basis/Moonlight/Core/MapAccum.hs

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE StrictData #-}

-- | Strict map building and accumulation helpers: indexing, grouping,
-- accumulating by key, and triple indexing.
module Moonlight.Core.MapAccum
  ( indexMap
  , groupByKey
  , accumByKey
  , buildTripleIndex
  ) where

import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Prelude
  ( Int
  , Ord
  , Semigroup ((<>))
  , foldl'
  , foldr
  , reverse
  , zip
  , (++)
  )

-- | Index values by zero-based input position.
--
-- Duplicate keys follow a last-occurrence-wins law: the stored index is the
-- final position at which the key appears in the input list.
indexMap :: Ord a => [a] -> Map a Int
indexMap values = Map.fromList (zip values [0 ..])

groupByKey :: Ord k => (a -> k) -> [a] -> Map k [a]
groupByKey keyOf = foldr (insertGroupedValue keyOf) Map.empty

accumByKey :: (Ord k, Semigroup v) => (a -> k) -> (a -> v) -> [a] -> Map k v
accumByKey keyOf valueOf =
  foldl'
    (\acc value -> Map.insertWith (\newValue oldValue -> oldValue <> newValue) (keyOf value) (valueOf value) acc)
    Map.empty

buildTripleIndex ::
  (Ord k1, Ord k2, Ord k3) =>
  (a -> k1) -> (a -> k2) -> (a -> k3) ->
  [a] ->
  (Map k1 [a], Map k2 [a], Map k3 [a])
buildTripleIndex keyOf1 keyOf2 keyOf3 values =
  tripleIndexAccumulatorMaps
    (foldl' (accumulateTripleIndex keyOf1 keyOf2 keyOf3) emptyTripleIndexAccumulator values)

data TripleIndexAccumulator k1 k2 k3 a
  = TripleIndexAccumulator !(Map k1 [a]) !(Map k2 [a]) !(Map k3 [a])

emptyTripleIndexAccumulator :: TripleIndexAccumulator k1 k2 k3 a
emptyTripleIndexAccumulator =
  TripleIndexAccumulator Map.empty Map.empty Map.empty

accumulateTripleIndex ::
  (Ord k1, Ord k2, Ord k3) =>
  (a -> k1) -> (a -> k2) -> (a -> k3) ->
  TripleIndexAccumulator k1 k2 k3 a ->
  a ->
  TripleIndexAccumulator k1 k2 k3 a
accumulateTripleIndex keyOf1 keyOf2 keyOf3 (TripleIndexAccumulator index1 index2 index3) value =
  TripleIndexAccumulator
    (insertGroupedValue keyOf1 value index1)
    (insertGroupedValue keyOf2 value index2)
    (insertGroupedValue keyOf3 value index3)

tripleIndexAccumulatorMaps :: TripleIndexAccumulator k1 k2 k3 a -> (Map k1 [a], Map k2 [a], Map k3 [a])
tripleIndexAccumulatorMaps (TripleIndexAccumulator index1 index2 index3) =
  (Map.map reverse index1, Map.map reverse index2, Map.map reverse index3)

insertGroupedValue :: Ord k => (a -> k) -> a -> Map k [a] -> Map k [a]
insertGroupedValue keyOf value =
  Map.insertWith (++) (keyOf value) [value]