large-anon-0.3.4: src/Data/Record/Anon/Internal/Plugin/TC/EquivClasses.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Data.Record.Anon.Internal.Plugin.TC.EquivClasses (
constructEquivClasses
, canonicalize
) where
import Data.Bifunctor
import Data.Foldable (toList)
import Data.Graph (Graph, Vertex)
import Data.Map (Map)
import Data.Set (Set)
import qualified Data.Graph as Graph
import qualified Data.Map as Map
import qualified Data.Set as Set
-- | Given a set of equivalent pairs, map every value to canonical value
--
-- Example with two classes:
--
-- >>> constructEquivClasses [(1, 2), (4, 5), (2, 3)]
-- fromList [(1,1),(2,1),(3,1),(4,4),(5,4)]
--
-- Adding one element that connects both classes:
--
-- >>> constructEquivClasses [(1, 2), (4, 5), (2, 3), (3, 4)]
-- fromList [(1,1),(2,1),(3,1),(4,1),(5,1)]
constructEquivClasses :: forall a. Ord a => [(a, a)] -> Map a a
constructEquivClasses equivs =
Map.unions $ map (pickCanonical . map fromVertex . toList) $
Graph.components graph
where
allValues :: Set a
allValues = Set.fromList $ concatMap (\(x, y) -> [x, y]) equivs
toVertex :: a -> Vertex
fromVertex :: Vertex -> a
toVertex a = Map.findWithDefault (error "toVertex: impossible") a $
Map.fromList $ zip (Set.toList allValues) [1..]
fromVertex v = Map.findWithDefault (error "fromVertex: impossible") v $
Map.fromList $ zip [1..] (Set.toList allValues)
graph :: Graph
graph = Graph.buildG (1, Set.size allValues) $
map (bimap toVertex toVertex) equivs
-- Given a previously established equivalence class, construct a mapping
-- that maps each value to an (arbitrary) canonical value.
pickCanonical :: [a] -> Map a a
pickCanonical cls = Map.fromList $ zip cls (repeat (minimum cls))
canonicalize :: Ord a => Map a a -> a -> a
canonicalize canon x = Map.findWithDefault x x canon