{- Copyright (C) 2018 Dr. Alistair Ward This file is part of BishBosh. BishBosh is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. BishBosh is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BishBosh. If not, see . -} {- | [@AUTHOR@] Dr. Alistair Ward [@DESCRIPTION@] Defines operations on an arbitrary rose-tree. -} module BishBosh.Data.RoseTree( -- * Types -- ** Type-synonyms -- Transformation, IsMatch, -- * Function countTerminalNodes, drawTree, drawForest, traceRoute, -- ** Mutators promote, reduce, mapForest ) where import qualified Data.List import qualified Data.Tree -- | Counts the number of terminal nodes. countTerminalNodes :: Num nodes => Data.Tree.Tree a -> nodes countTerminalNodes Data.Tree.Node { Data.Tree.subForest = [] } = 1 countTerminalNodes Data.Tree.Node { Data.Tree.subForest = forest } = Data.List.foldl' ( \acc -> (+ acc) . countTerminalNodes {-recurse-} ) 0 forest -- | Returns a string which graphically represents the tree. drawTree :: (a -> String) -> Data.Tree.Tree a -> String drawTree toString = Data.Tree.drawTree . fmap toString -- | Returns a string which graphically represents the forest. drawForest :: (a -> String) -> Data.Tree.Forest a -> String drawForest toString = Data.Tree.drawForest . map (fmap toString) -- | Whether a datum matches. type IsMatch a = a -> Bool -- | Trace a path down the specified tree, of matching nodes. traceRoute :: (datum -> IsMatch a) -- ^ Whether a datum matches. -> Data.Tree.Tree a -> [datum] -- ^ The data against which, nodes from the tree should be matched. -> Maybe [a] -- ^ Returns 'Nothing' on match-failure. traceRoute isMatch = slave . Data.Tree.subForest where slave forest (datum : remainingData) = Data.List.find ( isMatch datum . Data.Tree.rootLabel ) forest >>= ( \Data.Tree.Node { Data.Tree.rootLabel = rootLabel, Data.Tree.subForest = subForest } -> (rootLabel :) `fmap` slave subForest remainingData {-recurse-} ) slave _ _ = Just [] {- | * Recursively advances the position within the forest, of the first node which matches the next datum, at successively deeper levels. * CAVEAT: each datum is expected to match exactly one item from the forest at each level. -} promote :: (datum -> IsMatch a) -- ^ Whether a node matches. -> [datum] -- ^ The data against which nodes from the forest should be matched. -> [Data.Tree.Tree a] -> Maybe [Data.Tree.Tree a] -- ^ Returns 'Nothing' on match-failure. promote isMatch = slave where slave (datum : remainingData) forest = case break (isMatch datum . Data.Tree.rootLabel) forest of (mismatches, match@Data.Tree.Node { Data.Tree.subForest = forest' } : remainingNodes) -> ( \forest'' -> match { Data.Tree.subForest = forest'' } : mismatches ++ remainingNodes ) `fmap` slave remainingData forest' -- Recurse. _ -> Nothing -- Match-failure. slave _ forest = Just forest -- Data exhausted => Terminate normally. -- | Reduce the tree to the first matching datum in the forest. reduce :: IsMatch a -> Data.Tree.Tree a -> Maybe (Data.Tree.Tree a) reduce isMatch Data.Tree.Node { Data.Tree.subForest = subForest } = Data.List.find (isMatch . Data.Tree.rootLabel) subForest -- | The type of a function which changes the structure (but not the type) of the specified tree. type Transformation a = Data.Tree.Tree a -> Data.Tree.Tree a {- | * Apply an arbitrary mapping to all subForests; cf 'fmap' which applies an arbitrary function to all rootLabels. * The mapping is given access to the label at each forest. -} mapForest :: (a -> Data.Tree.Forest a -> Data.Tree.Forest a) -> Transformation a mapForest f = slave where slave node@Data.Tree.Node { Data.Tree.rootLabel = label, Data.Tree.subForest = forest } = node { Data.Tree.subForest = map slave {-recurse-} $ f label forest }