immutaball-core-0.1.0.5.1: Data/LabeledBinTree.hs
{-# OPTIONS_GHC -fno-warn-tabs #-} -- Support tab indentation better, for a better default of no warning if tabs are used: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces .
-- Enable warnings:
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
-- CLI.hs.
{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE TemplateHaskell, InstanceSigs #-}
module Data.LabeledBinTree
(
BinTree,
BinTreeF(..),
BinTreeLabeled,
LabeledBinTree(..), labeledBinTree,
Tree,
deconsLabeledBinTree,
mkLabeledEmpty,
mkLabeledLeaf,
mkLabeledFork,
fmapLabeledBinTree,
foldrLabeledBinTree,
simplifyLeavesLabeledBinTree,
simplifyEmptiesLabeledBinTree,
normalizeLabeledBinTree,
forkinizeLeaves,
forkinizeLeavesDirect,
joinLabeledBinTree,
joinDirectLabeledBinTree,
singletonLabeledBin,
repeatLabeledBinTree,
pureLabeledBinTreeLossy,
appLabeledBinTreeLossy,
labeledBinTreeConcatRightmost,
pureLabeledBinTreeCombinatorial,
appLabeledBinTreeCombinatorial,
mapLeavesDirectLabeledBinTree,
mapLeavesLabeledBinTree,
mapNonleavesLabeledBinTree,
bindLabeledBinTree,
traverseLBT,
-- * Sizes
numElemsLBT,
numNodesWeightedDirectLBT,
numNodesDirectLBT,
numEmptyDirectLBT,
numLeavesDirectLBT,
numForkDirectLBT,
numAnyDirectLBT,
numElemsLBTI,
numNodesWeightedDirectLBTI,
numNodesDirectLBTI,
numEmptyDirectLBTI,
numLeavesDirectLBTI,
numForkDirectLBTI,
numAnyDirectLBTI,
-- * Constructor aliases
emptyLBT,
leafLBT,
forkLBT,
-- * Equivalence relations
eqLBT,
leqLBT,
-- * Utils
showFs,
showFs'
) where
import Prelude ()
import Immutaball.Prelude
import Data.Function hiding (id, (.))
import Control.Lens
import Immutaball.Share.Utils
type BinTree n l = Fixed (BinTreeF n l)
data BinTreeF n l me =
-- | Empty node.
EmptyBT
-- | Leaf node.
| LeafBT l
-- | Fork node.
| ForkBT me n me
deriving (Eq, Ord, Show)
type BinTreeLabeled a = BinTree a a
newtype LabeledBinTree a = LabeledBinTree {_labeledBinTree :: BinTreeLabeled a}
deriving (Eq, Ord, Show)
makeLenses ''LabeledBinTree
type Tree = LabeledBinTree
deconsLabeledBinTree :: r -> (a -> r) -> (LabeledBinTree a -> a -> LabeledBinTree a -> r) -> LabeledBinTree a -> r
deconsLabeledBinTree withEmptyBT _ _ (LabeledBinTree (Fixed (EmptyBT ))) = withEmptyBT
deconsLabeledBinTree _ withLeafBT _ (LabeledBinTree (Fixed (LeafBT a ))) = withLeafBT a
deconsLabeledBinTree _ _ withForkBT (LabeledBinTree (Fixed (ForkBT l a r))) = withForkBT (LabeledBinTree l) a (LabeledBinTree r)
mkLabeledEmpty :: LabeledBinTree a
mkLabeledEmpty = LabeledBinTree . Fixed $ EmptyBT
mkLabeledLeaf :: a -> LabeledBinTree a
mkLabeledLeaf a = LabeledBinTree . Fixed $ LeafBT a
mkLabeledFork :: LabeledBinTree a -> a -> LabeledBinTree a -> LabeledBinTree a
mkLabeledFork l a r = LabeledBinTree . Fixed $ ForkBT (_labeledBinTree l) a (_labeledBinTree r)
fmapLabeledBinTree :: (a -> b) -> (LabeledBinTree a -> LabeledBinTree b)
fmapLabeledBinTree f = deconsLabeledBinTree (mkLabeledEmpty) (\a -> mkLabeledLeaf (f a)) (\l a r -> mkLabeledFork (fmapLabeledBinTree f l) (f a) (fmapLabeledBinTree f r))
foldrLabeledBinTree :: (a -> b -> b) -> b -> LabeledBinTree a -> b
foldrLabeledBinTree f z = deconsLabeledBinTree
z
(\a -> f a z)
(\l a r -> foldrLabeledBinTree f (f a (foldrLabeledBinTree f z r)) l)
-- | Replace all forks of empty nodes with leaf nodes.
--
-- We can do this because 'LabeledBinTree' requires leaf and fork node
-- element types to be qual.
simplifyLeavesLabeledBinTree :: LabeledBinTree a -> LabeledBinTree a
simplifyLeavesLabeledBinTree = deconsLabeledBinTree
mkLabeledEmpty
(\a -> mkLabeledLeaf a)
(\l a r -> deconsLabeledBinTree
(deconsLabeledBinTree
(mkLabeledLeaf a) -- Replacement: both original left and right trees are empty.
(\_ra -> mkLabeledFork (simplifyLeavesLabeledBinTree l) a (simplifyLeavesLabeledBinTree r))
(\_rl _ra _rr -> mkLabeledFork (simplifyLeavesLabeledBinTree l) a (simplifyLeavesLabeledBinTree r))
r
)
(\_la -> mkLabeledFork (simplifyLeavesLabeledBinTree l) a (simplifyLeavesLabeledBinTree r))
(\_ll _la _lr -> mkLabeledFork (simplifyLeavesLabeledBinTree l) a (simplifyLeavesLabeledBinTree r))
l
)
-- | Simplify a LabeledBinTree tree by removing all Leave constructs, replacing
-- leaves with forks of empties. The values of leaves is not lost; they are
-- simply reframed as forks of empties (with the value still present).
simplifyEmptiesLabeledBinTree :: LabeledBinTree a -> LabeledBinTree a
simplifyEmptiesLabeledBinTree = deconsLabeledBinTree
mkLabeledEmpty
(\a -> mkLabeledFork mkLabeledEmpty a mkLabeledEmpty)
(\l a r -> mkLabeledFork l a r )
--- | Normalize a LabeledBinTree with 'simplifyLeavesLabeledBinTree',
--- replacing all forks of empty nodes with leaf nodes.
normalizeLabeledBinTree :: LabeledBinTree a -> LabeledBinTree a
normalizeLabeledBinTree = simplifyLeavesLabeledBinTree
-- | Replace all leaf nodes with a fork node with the given left and right
-- branches.
--
-- Note: normalizing here doesn't change the result if we were to instead check
-- for leaf equivalence manually ourselves.
forkinizeLeaves :: LabeledBinTree a -> LabeledBinTree a -> LabeledBinTree a -> LabeledBinTree a
forkinizeLeaves ll base lr = forkinizeLeavesDirect ll (normalizeLabeledBinTree base) lr
-- | Assuming the base tree is already normalized, replace all leaf nodes in
-- the base tree with a fork node with the given left and right branches.
forkinizeLeavesDirect :: LabeledBinTree a -> LabeledBinTree a -> LabeledBinTree a -> LabeledBinTree a
forkinizeLeavesDirect ll base lr = deconsLabeledBinTree
mkLabeledEmpty
(\a -> mkLabeledFork ll a lr)
(\l a r -> mkLabeledFork (forkinizeLeavesDirect ll l lr) a (forkinizeLeavesDirect ll r lr))
base
-- | Replace each of the root tree's inner leaf nodes with a fork node whose
-- left branch is the likewise joined outer left tree and whose right branch is
-- the likewise joined outer right tree. This is perhaps like a
-- non-determinism of paths to leaves, perhaps somewhat like list monad
-- non-determinism.
joinLabeledBinTree :: LabeledBinTree (LabeledBinTree a) -> LabeledBinTree a
joinLabeledBinTree = joinDirectLabeledBinTree . normalizeLabeledBinTree . (normalizeLabeledBinTree <$>)
-- | 'joinLabeledBinTree' that assumes the tree and all nested trees have
-- already been normalized.
joinDirectLabeledBinTree :: LabeledBinTree (LabeledBinTree a) -> LabeledBinTree a
joinDirectLabeledBinTree = deconsLabeledBinTree
mkLabeledEmpty
(\a -> a)
(\l a r -> forkinizeLeavesDirect (joinDirectLabeledBinTree l) a (joinDirectLabeledBinTree r))
singletonLabeledBin :: a -> LabeledBinTree a
singletonLabeledBin x = mkLabeledLeaf x
repeatLabeledBinTree :: a -> LabeledBinTree a
repeatLabeledBinTree x = mkLabeledFork (repeatLabeledBinTree x) x (repeatLabeledBinTree x)
pureLabeledBinTreeLossy :: a -> LabeledBinTree a
pureLabeledBinTreeLossy x = repeatLabeledBinTree x
-- | Zip-like lossy Applicative for LabeledBinTree: component-wise.
appLabeledBinTreeLossy :: (LabeledBinTree (a -> b)) -> LabeledBinTree a -> LabeledBinTree b
appLabeledBinTreeLossy mf ma = deconsLabeledBinTree
(mkLabeledEmpty) -- Discard any remaining elements of ‘ma’.
(\f ->
deconsLabeledBinTree
(mkLabeledEmpty) -- Discard the remaining elements of ‘mf’.
(\a -> mkLabeledLeaf (f a))
(\_ a _ -> mkLabeledLeaf (f a)) -- Discard the remaining elements of ‘ma’.
ma
)
(\fl f fr ->
deconsLabeledBinTree
(mkLabeledEmpty) -- Discard the remaining elements of ‘mf’.
(\a -> mkLabeledLeaf (f a)) -- Discard the remaining elements of ‘mf’.
(\al a ar -> mkLabeledFork
(appLabeledBinTreeLossy fl al)
(f a)
(appLabeledBinTreeLossy fr ar)
)
ma
)
mf
-- | Add another tree to the right-most leaf of this tree.
--
-- Keep following the right-most node until empty is found, and then replace it
-- with the sub-tree.
labeledBinTreeConcatRightmost :: LabeledBinTree a -> LabeledBinTree a -> LabeledBinTree a
labeledBinTreeConcatRightmost base additional = deconsLabeledBinTree
additional
(\a -> mkLabeledFork mkLabeledEmpty a additional)
(\l a r -> mkLabeledFork l a (labeledBinTreeConcatRightmost r additional))
base
pureLabeledBinTreeCombinatorial :: a -> LabeledBinTree a
pureLabeledBinTreeCombinatorial x = mkLabeledLeaf x
-- | List-like combinatorial Applicative for LabeledBinTree, with a focus on
-- leaves.
--
-- Note: since ‘m >>= f = join (f <$> m)’
-- and ‘m1 <*> m2 = m1 >>= (\x1 -> m2 >>= (\x2 -> return (x1 x2)))’,
-- then applying a binary tree of integer unaops (e.g. plus 1, times 2, squared
-- plus 3, etc.) to a binary tree of integers, would mean ‘m1 <*> m2’ is:
-- map, for each binop: map, for each integer: singleton of the binop applied
-- to the integer; then join; then join. Before the first join, we have a
-- binary tree of a binary tree of a binary tree: a binary tree of, for each of
-- the unaops, a binary tree of the integers, except the integers are singleton
-- applications of the function to the integer. Joining a binary tree of
-- singletons returns the same binary tree without the singleton wrappers (join
-- composed with pure), so after the first, inner join, we then have: a binary
-- tree of, for each of the unaops, a binary tree of the original integers each
-- applied to that unaop. (e.g. copy the integer tree for each node in the
-- unaop tree, and apply that unop.) We now have a binary tree of binary
-- trees, where each node in the outer tree represents which unary operation /
-- function we applied to the inner list to get the inner tree output for that
-- unaop.
--
-- Finally, given this binary tree of outputs for each unaop, we perform
-- another join: take the root binary tree, representing, say, the mapping of
-- the ‘plus1’ root unaop to the input integers, and then for each leaf, turn
-- it into a fork node where the left gets you a continued pattern for the
-- root's left unaops tree, and likewise for right.
--
-- So the result is you start with the root node function applied to the
-- argument tree, but once you reach any leaf, you can keep descending either
-- left or right (possibly a branch can be Empty), and repeat the original
-- argument/input tree except for different unaops. e.g. go to a leaf for the
-- root unaop, then go left, go to a would-be leaf, then go left again, and
-- then you start the tree for the unaop at the unaop tree's root's left's left
-- node's function.
appLabeledBinTreeCombinatorial :: LabeledBinTree (a -> b) -> LabeledBinTree a -> LabeledBinTree b
appLabeledBinTreeCombinatorial mf ma = joinLabeledBinTree ((\f -> (\a -> f a) <$> ma) <$> mf)
-- | Map leaf nodes, only checking directly for leaves. Normalizing the tree
-- can yield different results. To also map nodes that would be leaves when
-- normalized, use 'mapLeavesLabeledBinTree'.
mapLeavesDirectLabeledBinTree :: (a -> a) -> LabeledBinTree a -> LabeledBinTree a
mapLeavesDirectLabeledBinTree f = deconsLabeledBinTree
mkLabeledEmpty
(\a -> mkLabeledLeaf (f a))
(\l a r -> mkLabeledFork (mapLeavesDirectLabeledBinTree f l) a (mapLeavesDirectLabeledBinTree f r))
-- | 'mapLeavesDirectLabeledBinTree' but treats forks of empty nodes as leaves.
mapLeavesLabeledBinTree :: (a -> a) -> LabeledBinTree a -> LabeledBinTree a
mapLeavesLabeledBinTree f = deconsLabeledBinTree
mkLabeledEmpty
(\a -> mkLabeledLeaf (f a))
--(\l a r -> mkLabeledFork (mapLeavesDirectLabeledBinTree l) a (mapLeavesDirectLabeledBinTree r))
(\l a r -> deconsLabeledBinTree
(deconsLabeledBinTree
( mkLabeledFork (mapLeavesDirectLabeledBinTree f l) (f a) (mapLeavesDirectLabeledBinTree f r))
(\_ra -> mkLabeledFork (mapLeavesDirectLabeledBinTree f l) a (mapLeavesDirectLabeledBinTree f r))
(\_rl _ra _rr -> mkLabeledFork (mapLeavesDirectLabeledBinTree f l) a (mapLeavesDirectLabeledBinTree f r))
r
)
(\_la -> mkLabeledFork (mapLeavesDirectLabeledBinTree f l) a (mapLeavesDirectLabeledBinTree f r))
(\_ll _la _lr -> mkLabeledFork (mapLeavesDirectLabeledBinTree f l) a (mapLeavesDirectLabeledBinTree f r))
l
)
-- | Map each element, skipping leaves.
mapNonleavesLabeledBinTree :: (a -> a) -> LabeledBinTree a -> LabeledBinTree a
mapNonleavesLabeledBinTree f = deconsLabeledBinTree
mkLabeledEmpty
mkLabeledLeaf
(\l a r -> mkLabeledFork (mapNonleavesLabeledBinTree f l) (f a) (mapNonleavesLabeledBinTree f r))
-- | For each element in the (left) tree, replace with the tree produced by
-- applying the function to that element, where each leaf becomes a fork node
-- with its left and right children having likewise binding.
--
-- See 'joinLabeledBinTree' for more information.
bindLabeledBinTree :: LabeledBinTree a -> (a -> LabeledBinTree b) -> LabeledBinTree b
bindLabeledBinTree ma f = joinLabeledBinTree (f <$> ma)
instance Functor LabeledBinTree where
fmap :: (a -> b) -> (LabeledBinTree a -> LabeledBinTree b)
fmap = fmapLabeledBinTree
instance Foldable LabeledBinTree where
foldr :: (a -> b -> b) -> b -> LabeledBinTree a -> b
foldr = foldrLabeledBinTree
-- | The default Applicative instance of LabeledBinTree is 'appLabeledBinTreeCombinatorial'.
instance Applicative LabeledBinTree where
pure :: a -> LabeledBinTree a
pure = pureLabeledBinTreeCombinatorial
(<*>) :: LabeledBinTree (a -> b) -> LabeledBinTree a -> LabeledBinTree b
(<*>) = appLabeledBinTreeCombinatorial
-- | The default Monad instance of LabeledBinTree uses 'joinLabeledBinTree'
--
-- If my hand-wavy analysis is correct, joining trees strictly would have
-- exponentially growing space costs, so it may rarely be useful to use this
-- if evaluating the entire tree, rather than only down a specific or select
-- set of paths through the tree.
instance Monad LabeledBinTree where
return :: a -> LabeledBinTree a
return = pure
(>>=) :: LabeledBinTree a -> (a -> LabeledBinTree b) -> LabeledBinTree b
(>>=) = bindLabeledBinTree
traverseLBT :: Applicative f => (a -> f b) -> LabeledBinTree a -> f (LabeledBinTree b)
traverseLBT f = deconsLabeledBinTree
( pure emptyLBT)
(\a -> pure leafLBT <*> f a)
(\l a r -> pure forkLBT <*> traverseLBT f l <*> f a <*> traverseLBT f r)
instance Traversable LabeledBinTree where
traverse :: Applicative f => (a -> f b) -> LabeledBinTree a -> f (LabeledBinTree b)
traverse = traverseLBT
-- * Sizes
-- | The number of non-empty elements in an lbt.
numElemsLBT :: (Num i) => LabeledBinTree a -> i
numElemsLBT = foldr (\_x acc -> acc + 1) 0
-- | The number of nodes, empty, leaf, or forking.
--
-- Provide the size of empty, leaf, and forking nodes (e.g. 0, 1, 1) and find
-- the sum.
numNodesWeightedDirectLBT :: (Num i) => i -> i -> i -> LabeledBinTree a -> i
numNodesWeightedDirectLBT a b c tree0 = (\f -> f 0 [tree0]) . fix $ \count counter remaining -> case remaining of
[] -> counter
(tree:remaining') -> deconsLabeledBinTree
( count (counter + a) $ remaining')
(\_a -> count (counter + b) $ remaining')
(\l _a r -> count (counter + c) $ l:r:remaining')
tree
-- | The number of nodes, empty, leaf, or forking.
numNodesDirectLBT :: (Num i) => LabeledBinTree a -> i
numNodesDirectLBT = numNodesWeightedDirectLBT 1 1 1
-- | Count the number of empty nodes.
numEmptyDirectLBT :: (Num i) => LabeledBinTree a -> i
numEmptyDirectLBT = numNodesWeightedDirectLBT 1 0 0
-- | Count the number of leaf nodes.
numLeavesDirectLBT :: (Num i) => LabeledBinTree a -> i
numLeavesDirectLBT = numNodesWeightedDirectLBT 0 1 0
-- | Count the number of fork nodes (branching).
numForkDirectLBT :: (Num i) => LabeledBinTree a -> i
numForkDirectLBT = numNodesWeightedDirectLBT 0 0 1
-- | Count the number of nodes in the tree of any of the 3 kinds.
numAnyDirectLBT :: (Num i) => LabeledBinTree a -> i
numAnyDirectLBT = numNodesWeightedDirectLBT 1 1 1
-- | 'numElemsLBT' specialized to Integer.
numElemsLBTI :: LabeledBinTree a -> Integer
numElemsLBTI = numElemsLBT
-- | 'numNodesWeightedDirectLBT' specialized to Integer.
numNodesWeightedDirectLBTI :: Integer -> Integer -> Integer -> LabeledBinTree a -> Integer
numNodesWeightedDirectLBTI = numNodesWeightedDirectLBT
-- | 'numNodesDirectLBT' specialized to Integer.
numNodesDirectLBTI :: LabeledBinTree a -> Integer
numNodesDirectLBTI = numNodesDirectLBT
-- | 'numEmptyDirectLBT' specialized to Integer.
numEmptyDirectLBTI :: LabeledBinTree a -> Integer
numEmptyDirectLBTI = numEmptyDirectLBT
-- | 'numLeavesDirectLBT' specialized to Integer.
numLeavesDirectLBTI :: LabeledBinTree a -> Integer
numLeavesDirectLBTI = numLeavesDirectLBT
-- | 'numForkDirectLBT' specialized to Integer.
numForkDirectLBTI :: LabeledBinTree a -> Integer
numForkDirectLBTI = numForkDirectLBT
-- | 'numAnyDirectLBT' specialized to Integer.
numAnyDirectLBTI :: LabeledBinTree a -> Integer
numAnyDirectLBTI = numAnyDirectLBT
-- * Constructor aliases
-- | Shorter alias of 'mkLabeledEmpty'.
emptyLBT :: LabeledBinTree a
emptyLBT = mkLabeledEmpty
-- | Shorter alias of 'mkLabeledLeaf'.
leafLBT :: a -> LabeledBinTree a
leafLBT = mkLabeledLeaf
-- | Shorter alias of 'mkLabeledFork'.
forkLBT :: LabeledBinTree a -> a -> LabeledBinTree a -> LabeledBinTree a
forkLBT = mkLabeledFork
-- * Equivalence relations
-- | Equivalence after normalization.
eqLBT :: (Eq a) => LabeledBinTree a -> LabeledBinTree a -> Bool
eqLBT = (==) `on` normalizeLabeledBinTree
-- | Leq after normalization.
leqLBT :: (Ord a) => LabeledBinTree a -> LabeledBinTree a -> Bool
leqLBT = (<=) `on` normalizeLabeledBinTree
-- | fs-style show.
showFs :: (Show a) => LabeledBinTree a -> String
showFs = showFs' show
-- | fs-style show, with a custom Show instance of ‘a’; primitive but still
-- more readable than default Show.
--
-- This implementation is currently relatively inefficient, since there is a
-- lot of unneeded repeated concatenation. Consider DLists if wanting to
-- improve.
showFs' :: (a -> String) -> LabeledBinTree a -> String
showFs' showA tree = (\f -> f "" tree) . fix $ \withPrefix prefix subtree -> deconsLabeledBinTree
(prefix ++ "×\n")
(\a -> prefix ++ (showA a) ++ "\n")
(\l a r -> prefix ++ showA a ++ "\n" ++ withPrefix (prefix ++ "\t.") l ++ withPrefix (prefix ++ "\t·") r)
subtree