Skip to main content

AlgoTree: Immutable Tree Structures with Functional Transformers

AlgoTree provides a modern, functional approach to working with tree structures in Python. Version 2.0 is a complete redesign built on immutable-by-default principles with composable transformers and pattern-matching selectors.

Why Immutable Trees?

Mutable tree libraries have hidden costs:

  • Side effects: Modifying a tree can break other code holding references
  • Debugging difficulty: Hard to track when/where a tree changed
  • Thread unsafety: Concurrent modifications cause subtle bugs

AlgoTree takes a different approach: all operations return new tree objects. The original is never modified.

from AlgoTree import Node, node

# Build a tree
tree = node("root",
    node("child1", value=1),
    node("child2", value=2)
)

# All operations return new trees
tree2 = tree.with_name("new_root")  # tree unchanged
tree3 = tree.with_child(Node("child3"))  # tree unchanged

Building Trees

Multiple construction styles for different use cases:

from AlgoTree import Node, node, TreeBuilder

# Simple construction with Node
tree = Node("root",
    Node("child1", attrs={"value": 1}),
    Node("child2", attrs={"value": 2})
)

# Convenience function (auto-converts strings)
tree = node("root",
    node("child1", value=1),
    "child2",  # Strings auto-convert to nodes
    node("child3",
        "grandchild1",
        "grandchild2"
    )
)

# Fluent builder API
tree = (TreeBuilder("root", type="container")
    .child("src")
        .child("main.py", type="file", size=1024)
        .child("utils.py", type="file", size=512)
        .up()
    .child("docs")
        .child("README.md", type="file")
    .build())

Functional Transformations

Apply functional operations across entire trees:

# Map: transform all nodes
doubled = tree.map(lambda n: n.with_attrs(
    value=n.get("value", 0) * 2
))

# Filter: keep nodes matching predicate
filtered = tree.filter(lambda n: n.get("value", 0) > 5)

# Find: locate specific nodes
nodes = tree.find_all(lambda n: n.is_leaf)

Composable Selectors

Pattern matching with wildcards and composition:

from AlgoTree import name, attrs, leaf, type_

# Pattern matching with wildcards
selector = name("*.txt")

# Attribute matching with predicates
selector = attrs(size=lambda s: s > 1000)

# Logical composition with operators
selector = type_("file") & ~leaf()  # Files that aren't leaves

# Structural selectors
selector = type_("file").child_of(name("src"))
selector = leaf().at_depth(2)

# Use selectors with trees
matching_nodes = list(selector.select(tree))

Pipe-Based Transformers

Build transformation pipelines with the >> operator:

from AlgoTree import map_, filter_, prune, normalize, extract

# Build transformation pipelines
pipeline = (
    map_(lambda n: {"processed": True}) >>
    filter_(lambda n: n.get("active")) >>
    normalize(sort_children=True) >>
    extract(lambda n: n.name)
)

# Apply pipeline to tree
result = pipeline(tree)

Export Formats

Rich export capabilities:

# JSON export
json_str = tree.to_json(indent=2)

# GraphViz for visualization
dot_str = tree.to_graphviz()

# Mermaid diagrams
mermaid_str = tree.to_mermaid()

# Path listings
paths = tree.to_paths()

Relationship to AlgoGraph

AlgoTree and AlgoGraph share the same design philosophy:

  • Immutable data structures
  • Pipe-based transformers
  • Declarative selectors
  • Lazy views

They also interoperate:

from AlgoTree import Tree
from AlgoGraph import tree_to_graph, graph_to_tree

# Tree to Graph
graph = tree_to_graph(tree)

# Graph to Tree (extracts spanning tree)
tree = graph_to_tree(graph, root='root')

Installation

pip install AlgoTree

Resources


AlgoTree: Functional programming meets tree structures. Immutable, composable, and elegant.

Discussion