Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.14.6] - 2023-12-14
### Added
- Node: Able to access and delete node children via name with square bracket accessor with `__getitem__` and `__delitem__` magic methods.
- BaseNode/Node/BinaryNode: Able to add one or more children with `append` and `extend` methods.
- BaseNode/Node/BinaryNode: Able to check if node contains child node with `__contains__` magic method.
- BaseNode/Node/BinaryNode: Able to iterate the node to access children with `__iter__` magic method. Results in children setter to only accept list/tuple/set instead of iterable types.
### Changed
- Tree Exporter: `tree_to_dot` accepts callable to set node and edge attrs for custom node (backward-compatible).
- Tree Exporter: `tree_to_mermaid` accepts callable to set node shape attr, edge arrow attr and node attr for custom node (backward-compatible).
- Tree Exporter: Change delimiter for `tree_to_mermaid` to prevent possible path confusion (backward-compatible).
- Misc: Code abstraction for assertion checks and constants.
- Misc: Documentation for exporting tree/dag to dot.

## [0.14.5] - 2023-11-24
### Changed
- Misc: Update SECURITY file.
Expand Down Expand Up @@ -384,6 +397,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Utility Iterator: Tree traversal methods.
- Workflow To Do App: Tree use case with to-do list implementation.

[0.14.6]: https://github.com/kayjan/bigtree/compare/0.14.5...0.14.6
[0.14.5]: https://github.com/kayjan/bigtree/compare/0.14.4...0.14.5
[0.14.4]: https://github.com/kayjan/bigtree/compare/0.14.3...0.14.4
[0.14.3]: https://github.com/kayjan/bigtree/compare/0.14.2...0.14.3
Expand Down
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ For **Directed Acyclic Graph (DAG)** implementation, there are 4 main components

There are two ways to install `bigtree`, with pip (from PyPI) or conda (from conda-forge).

### a) Installation with pip (preferred)
### a) Installation with pip (recommended)

To install `bigtree`, run the following line in command prompt:

Expand Down Expand Up @@ -158,8 +158,11 @@ Nodes can have attributes if they are initialized from `Node`, *dictionary*, or

1. **From `Node`**

Nodes can be linked to each other with `parent` and `children` setter methods,
or using bitshift operator with the convention `parent_node >> child_node` or `child_node << parent_node`.
Nodes can be linked to each other in the following ways:
- Using `parent` and `children` setter methods
- Directly passing `parent` or `children` argument
- Using bitshift operator with the convention `parent >> child` or `child << parent`
- Using `.append(child)` or `.extend([child1, child2])` methods

{emphasize-lines="8-9"}
```python
Expand Down Expand Up @@ -241,7 +244,7 @@ a
└── c
└── f
"""
root = str_to_tree(tree_str, tree_prefix_list=["├──", "└──"])
root = str_to_tree(tree_str)
root.show()
# a
# ├── b
Expand Down Expand Up @@ -369,7 +372,7 @@ root.show(attr_list=["age"])
# └── c [age=60]
```

> If tree is already created, attributes can still be added using dictionary or pandas DataFrame!
> If tree is already created, attributes can still be added using a dictionary or pandas DataFrame!

### Print Tree

Expand Down Expand Up @@ -652,9 +655,9 @@ root_other.show()

### Tree Search

One or multiple nodes can be search based on name, path, attribute value, or user-defined condition.
One or multiple nodes can be searched based on name, path, attribute value, or user-defined condition.

To find a single node,
To find a single node:

{emphasize-lines="12,15,18,21,24,27"}
```python
Expand Down Expand Up @@ -688,7 +691,7 @@ find_attr(root, "age", 40)
# Node(/a/c/d, age=40)
```

To find multiple nodes,
To find multiple nodes:

{emphasize-lines="12,15,18,21,24"}
```python
Expand Down
Binary file modified assets/custom_tree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/custom_tree_callable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion bigtree/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.14.5"
__version__ = "0.14.6"

from bigtree.binarytree.construct import list_to_binarytree
from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag
Expand Down
62 changes: 53 additions & 9 deletions bigtree/node/basenode.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import copy
from typing import Any, Dict, Iterable, List, Optional, Tuple, TypeVar
from typing import Any, Dict, Generator, Iterable, List, Optional, Set, Tuple, TypeVar

from bigtree.utils.exceptions import CorruptedTreeError, LoopError, TreeError
from bigtree.utils.iterators import preorder_iter
Expand Down Expand Up @@ -107,8 +107,10 @@ class BaseNode:
2. ``get_attr(attr_name: str)``: Get value of node attribute
3. ``set_attrs(attrs: dict)``: Set node attribute name(s) and value(s)
4. ``go_to(node: Self)``: Get a path from own node to another node from same tree
5. ``copy()``: Deep copy self
6. ``sort()``: Sort child nodes
5. ``append(node: Self)``: Add child to node
6. ``extend(nodes: List[Self])``: Add multiple children to node
7. ``copy()``: Deep copy self
8. ``sort()``: Sort child nodes

----

Expand Down Expand Up @@ -252,15 +254,21 @@ def parents(self, new_parent: T) -> None:
"Attempting to set `parents` attribute, do you mean `parent`?"
)

def __check_children_type(self: T, new_children: Iterable[T]) -> None:
def __check_children_type(
self: T, new_children: List[T] | Tuple[T] | Set[T]
) -> None:
"""Check child type

Args:
new_children (Iterable[Self]): child node
"""
if not isinstance(new_children, Iterable):
if (
not isinstance(new_children, list)
and not isinstance(new_children, tuple)
and not isinstance(new_children, set)
):
raise TypeError(
f"Expect children to be Iterable type, received input type {type(new_children)}"
f"Expect children to be List or Tuple or Set type, received input type {type(new_children)}"
)

def __check_children_loop(self: T, new_children: Iterable[T]) -> None:
Expand Down Expand Up @@ -303,7 +311,7 @@ def children(self: T) -> Tuple[T, ...]:
return tuple(self.__children)

@children.setter
def children(self: T, new_children: Iterable[T]) -> None:
def children(self: T, new_children: List[T] | Tuple[T] | Set[T]) -> None:
"""Set child nodes

Args:
Expand Down Expand Up @@ -638,6 +646,23 @@ def go_to(self: T, node: T) -> Iterable[T]:
node_min_index = node_path.index(min_common_node)
return self_path[:self_min_index] + node_path[node_min_index:]

def append(self: T, other: T) -> None:
"""Add other as child of self

Args:
other (Self): other node, child to be added
"""
other.parent = self

def extend(self: T, others: List[T]) -> None:
"""Add others as children of self

Args:
others (Self): other nodes, children to be added
"""
for child in others:
child.parent = self

def copy(self: T) -> T:
"""Deep copy self; clone self

Expand Down Expand Up @@ -698,20 +723,39 @@ def __repr__(self) -> str:
return f"{class_name}({node_description})"

def __rshift__(self: T, other: T) -> None:
"""Set children using >> bitshift operator for self >> other
"""Set children using >> bitshift operator for self >> children (other)

Args:
other (Self): other node, children
"""
other.parent = self

def __lshift__(self: T, other: T) -> None:
"""Set parent using << bitshift operator for self << other
"""Set parent using << bitshift operator for self << parent (other)

Args:
other (Self): other node, parent
"""
self.parent = other

def __iter__(self) -> Generator[T, None, None]:
"""Iterate through child nodes

Returns:
(Self): child node
"""
yield from self.children # type: ignore

def __contains__(self, other_node: T) -> bool:
"""Check if child node exists

Args:
other_node (T): child node

Returns:
(bool)
"""
return other_node in self.children


T = TypeVar("T", bound=BaseNode)
25 changes: 25 additions & 0 deletions bigtree/node/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,31 @@ def show(self, **kwargs: Any) -> None:

print_tree(self, **kwargs)

def __getitem__(self, child_name: str) -> T:
"""Get child by name identifier

Args:
child_name (str): name of child node

Returns:
(Self): child node
"""
from bigtree.tree.search import find_child_by_name

return find_child_by_name(self, child_name) # type: ignore

def __delitem__(self, child_name: str) -> None:
"""Delete child by name identifier, will not throw error if child does not exist

Args:
child_name (str): name of child node
"""
from bigtree.tree.search import find_child_by_name

child = find_child_by_name(self, child_name)
if child:
child.parent = None

def __repr__(self) -> str:
"""Print format of Node

Expand Down
Loading