dosarf / elm-tree-view / Tree

A module containing a tree (model) facility + handy functions. This tree is not a search tree (e.g. binary tree), it's for representing hierarchical information to users (e.g. folder-file structure, document structure, etc).

Traversal

Utility functions (currently) traverse the tree in depth-first, and children of a node in left to right order.

Node data structure


type Node d
    = Node ({ data : d, children : List (Node d) })

A tree node carrying some data. Can have children nodes.

Create a node like:

type alias MyData = { .. }

Node { data = MyData .., children = [ .. ] }

Utility functions

treeHeight : Node d -> Basics.Int

Calculates the height of a tree, which is the number of steps of the longest path to a leaf node. Since a tree consists of at least one node, the height of a tree is always at least 1.

forestHeight : List (Node d) -> Basics.Int

Calculates the height of a forest (list of trees), which is the height of the tallest tree in the forest. The calculated height value is 0 if the forest is empty of trees, otherwise always at least 1.

joinTree : (Node d -> String) -> String -> Node d -> String

Joins the text representation of all nodes of a tree into a single string, separated by given separator, in the traversal order. Will never be an empty string.

joinForest : (Node d -> String) -> String -> List (Node d) -> String

Joins the text representation of all nodes of a forest into a single string, separated by given separator, in left->right order. Can be an empty string in case the forest is empty of trees.


type alias AnnotatedNode d =
{ index : Basics.Int
, level : Basics.Int
, node : Node d 
}

A node annotated with zero-based index of the node, in the traversal order zero-based level of the node (level is less then the height of the tree the node is in) * the node itself.

listAnnotatedTreeNodes : Node d -> List (AnnotatedNode d)

Lists the nodes of a tree, annotated, in the traversal order. Will never be an empty list. See AnnotatedNode for what a node is annotated with.

listAnnotatedForestNodes : List (Node d) -> List (AnnotatedNode d)

Lists the nodes of a forest, annotated, in left-right order. Can be an empty list in case the forest is empty of trees. See AnnotatedNode for what a node is annotated with.

Getting stuff out of nodes

childrenOf : Node d -> List (Node d)

Retrieves the list of children of a node.

dataOf : Node d -> d

Retrieves the data stored within a node.

Updating node data

updateTreeData : (d -> Basics.Bool) -> (d -> d) -> Node d -> Node d

Updates data stored in the nodes of the tree, recursively. Leaves the structure of the tree intact.

tree : T.Node String
tree =
    ... -- construct tree with single string as data on nodes

-- turn uppercase the strings starting with 's' in all nodes
updateTreeData
    (\s -> String.startsWith "s" s)
    (String.toUpper)
    tree

updateForestData : (d -> Basics.Bool) -> (d -> d) -> List (Node d) -> List (Node d)

Updates data stored in the nodes of the trees in a list, recursively. Very similar to updateTreeData. Leaves the structure of the trees intact.

More utility functions

listTreeNodes : Node d -> List (Node d)

Lists the nodes of a tree, in the traversal order. Will never be an empty list.

listForestNodes : List (Node d) -> List (Node d)

Lists the nodes of a forest, in left-right order. Can be an empty list in case the forest is empty of trees.

Folding tree structures


type alias FoldOptions d foldState =
{ preFoldingThunk : foldState -> Node d -> foldState
, postFoldingThunk : foldState -> Node d -> foldState -> foldState
, childrenFoldingThunk : foldState -> Node d -> foldState -> foldState 
}

Fold options to use with foldTree or foldForest.

Think of the first argument to a List.foldl invocation ((a -> b -> b)), which is a function to fold the value of a list item with some previously obtained value.

In the case of folding an entire tree, more aggregator fold functions are needed.

Implement these aggregator functions as if they had the following declarations:

preFoldingThunk

preFoldingThunk : foldState -> Node d -> foldState
preFoldingThunk foldStateFromParent node =
    ...

Function FoldOptions.preFoldingThunk is used just before visiting the children of a node, and its first argument will be either the fold state handed down from the parent node or the initial fold fold state given to a root foldTree invocation.

postFoldingThunk

postFoldingThunk : foldState -> Node d -> foldState -> foldState
postFoldingThunk foldStateFromParent node previousFoldState =
    ...

Function FoldOptions.postFoldingThunk is used after visiting all children of a node, just before leaving the node.

Its first argument will be the same value given to FoldOptions.preFoldingThunk earlier (as a convenience).

The second argument will be the node we are about to leave.

The third argument will be either the fold state calculated by FoldOptions.preFoldingThunk if there are no child nodes of this node, or the fold state obtained by processing the last child node.

childrenFoldingThunk

childrenFoldingThunk : foldState -> Node d -> foldState -> foldState
childrenFoldingThunk previousFoldState node nodeFoldState =
    ...

Function FoldOptions.childrenFoldingThunk is used during visiting the children nodes of a node, or when folding a list of nodes by invoking foldForest.

Its first argument will be the fold state calculated by FoldOptions.preFoldingThunk if this is the first child node of a parent node, or the initial fold state given to the root foldForest invocation if the first node in the list of nodes, * or the fold state obtained by processing the previous sibling node (= the result of FoldOptions.postFoldingThunk produced by the foldTree invocation on the previous sibling node).

The second argument will be the node that just has been visited.

The third argument will be the fold value obtained from visiting that node.

Fold states example

For a simple tree with root node a,

a
|
+- b
+- c

the following fold states will be calculated for an invocation of foldTree myFoldOptions initialFoldState a:

and return eventually with fold state value fS8.

defaultFoldOptions : FoldOptions d foldState

Default fold options. On the off chance the default implementation of some fold state functions suits you, you may re-use them specifying only what you need:

myFoldOptions =
  { defaultFoldOptions
  | preFoldingThunk = \foldStateFromParent node -> ..
  }

The default fold state function implementations are:

essentially, the initial fold state value will be handed over, from calculation to calculation, and returned as the final fold state, without change.

foldTree : FoldOptions d foldState -> foldState -> Node d -> foldState

Folds a tree, similar to List.foldl. Children of a node are visited from left to right. Specifics of folding / aggregating are controlled by fold options, see FoldOptions and defaultFoldOptions.

foldForest : FoldOptions d foldState -> foldState -> List (Node d) -> foldState

Folds a forest (list of trees), visiting trees from left to right of the list.