Skip to content

Commit

Permalink
Merge pull request #308 from kayjan/bugfix/tree-diff
Browse files Browse the repository at this point in the history
Fix get_tree_diff
  • Loading branch information
kayjan authored Oct 16, 2024
2 parents c1c55a9 + 0dcc7f2 commit cf32279
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 13 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.21.3] - 2024-10-16
### Added:
- Tree Node: Docstring indentation and additional information for Node creation.
- Misc: GitHub star diagram to README file.
### Changed:
- Tree Helper: Get tree diff to handle `sep` that are different for tree and other_tree, this will throw error now.
- Tree Helper: Get tree diff to handle `sep` that contains forbidden symbols, this will change the `sep` to a fallback sep.
- Tree Helper: Get tree diff to sort tree before returning the tree diff.
### Fixed:
- [#306] Tree Helper: Get tree diff to handle `sep` that is different from default.

## [0.21.2] - 2024-10-14
### Added:
- Misc: Pull request template.
Expand Down Expand Up @@ -657,7 +668,8 @@ ignore null attribute columns.
- Utility Iterator: Tree traversal methods.
- Workflow To Do App: Tree use case with to-do list implementation.

[Unreleased]: https://github.com/kayjan/bigtree/compare/0.21.2...HEAD
[Unreleased]: https://github.com/kayjan/bigtree/compare/0.21.3...HEAD
[0.21.3]: https://github.com/kayjan/bigtree/compare/0.21.2...0.21.3
[0.21.2]: https://github.com/kayjan/bigtree/compare/0.21.1...0.21.2
[0.21.1]: https://github.com/kayjan/bigtree/compare/0.21.0...0.21.1
[0.21.0]: https://github.com/kayjan/bigtree/compare/0.20.1...0.21.0
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,9 @@ To install `bigtree` with conda, run the following line in command prompt:
```console
$ conda install -c conda-forge bigtree
```

-----

## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=kayjan/bigtree&type=Date)](https://star-history.com/#kayjan/bigtree&Date)
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.21.2"
__version__ = "0.21.3"

from bigtree.binarytree.construct import list_to_binarytree
from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag
Expand Down
29 changes: 26 additions & 3 deletions bigtree/tree/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ def get_tree_diff(
other_tree: node.Node,
only_diff: bool = True,
attr_list: List[str] = [],
fallback_sep: str = "/",
) -> node.Node:
"""Get difference of `tree` to `other_tree`, changes are relative to `tree`.
Expand All @@ -254,6 +255,12 @@ def get_tree_diff(
- For example: (+) refers to nodes that are in `other_tree` but not `tree`.
- For example: (-) refers to nodes that are in `tree` but not `other_tree`.
!!! note
- tree and other_tree must have the same `sep` symbol, otherwise this will raise ValueError
- If the `sep` symbol contains one of `+` / `-` / `~` character, a fallback sep will be used
- Node names in tree and other_tree must not contain the `sep` (or fallback sep) symbol
Examples:
>>> # Create original tree
>>> from bigtree import Node, get_tree_diff, list_to_tree
Expand Down Expand Up @@ -333,11 +340,25 @@ def get_tree_diff(
other_tree (Node): tree to be compared with
only_diff (bool): indicator to show all nodes or only nodes that are different (+/-), defaults to True
attr_list (List[str]): tree attributes to check for difference, defaults to empty list
fallback_sep (str): sep to fall back to if tree and other_tree has sep that clashes with symbols "+" / "-" / "~".
All node names in tree and other_tree should not contain this fallback_sep, defaults to "/"
Returns:
(Node)
"""
other_tree.sep = tree.sep
if tree.sep != other_tree.sep:
raise ValueError("`sep` must be the same for tree and other_tree")

forbidden_sep_symbols = ["+", "-", "~"]
if any(
forbidden_sep_symbol in tree.sep
for forbidden_sep_symbol in forbidden_sep_symbols
):
tree = tree.copy()
other_tree = other_tree.copy()
tree.sep = fallback_sep
other_tree.sep = fallback_sep

name_col = "name"
path_col = "PATH"
indicator_col = "Exists"
Expand Down Expand Up @@ -403,9 +424,11 @@ def get_tree_diff(
(data_both[indicator_col] != "both")
| (data_both[path_col].isin(path_changes_deque))
]
data_both = data_both[[path_col]]
data_both = data_both[[path_col]].sort_values(path_col)
if len(data_both):
tree_diff = construct.dataframe_to_tree(data_both, node_type=tree.__class__)
tree_diff = construct.dataframe_to_tree(
data_both, node_type=tree.__class__, sep=tree.sep
)
# Handle tree attribute difference
if len(path_changes_deque):
path_changes_list = sorted(path_changes_deque, reverse=True)
Expand Down
1 change: 1 addition & 0 deletions tests/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class Constants:
ERROR_NODE_PRUNE_NOT_FOUND = (
"Cannot find any node matching path_name ending with {prune_path}"
)
ERROR_NODE_TREE_DIFF_DIFF_SEP = "`sep` must be the same for tree and other_tree"

# tree/modify
ERROR_MODIFY_PARAM_TYPE = (
Expand Down
52 changes: 44 additions & 8 deletions tests/tree/test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,22 +236,58 @@ def test_tree_diff(tree_node):
assert_print_statement(export.print_tree, expected_str, tree=tree_only_diff)

@staticmethod
def test_tree_diff_diff_sep(tree_node):
def test_tree_diff_diff_sep_error(tree_node):
other_tree_node = helper.prune_tree(tree_node, "a/c")
_ = node.Node("d", parent=other_tree_node)
other_tree_node.sep = "-"
with pytest.raises(ValueError) as exc_info:
helper.get_tree_diff(tree_node, other_tree_node)
assert str(exc_info.value) == Constants.ERROR_NODE_TREE_DIFF_DIFF_SEP

@staticmethod
def test_tree_diff_sep_clash_with_node_name_error(tree_node):
other_tree_node = helper.prune_tree(tree_node, "a/c")
_ = node.Node("/d", parent=other_tree_node)
with pytest.raises(exceptions.TreeError) as exc_info:
helper.get_tree_diff(tree_node, other_tree_node)
assert str(exc_info.value) == Constants.ERROR_NODE_NAME

@staticmethod
def test_tree_diff_sep_clash_with_node_name(tree_node):
other_tree_node = helper.prune_tree(tree_node, "a/c")
_ = node.Node("/d", parent=other_tree_node)
tree_node.sep = "."
other_tree_node.sep = "."
tree_only_diff = helper.get_tree_diff(tree_node, other_tree_node)
expected_str = (
"a\n"
"├── b (-)\n"
"│ ├── d (-)\n"
" ── e (-)\n"
" ── g (-)\n"
" ── h (-)\n"
"└── d (+)\n"
"├── /d (+)\n"
"── b (-)\n"
" ── d (-)\n"
" ── e (-)\n"
" ── g (-)\n"
" └── h (-)\n"
)
assert_print_statement(export.print_tree, expected_str, tree=tree_only_diff)

@staticmethod
def test_tree_diff_forbidden_sep(tree_node):
other_tree_node = helper.prune_tree(tree_node, "a/c")
_ = node.Node("d", parent=other_tree_node)
for symbol in [".", "-", "+", "~"]:
tree_node.sep = symbol
other_tree_node.sep = symbol
tree_only_diff = helper.get_tree_diff(tree_node, other_tree_node)
expected_str = (
"a\n"
"├── b (-)\n"
"│ ├── d (-)\n"
"│ └── e (-)\n"
"│ ├── g (-)\n"
"│ └── h (-)\n"
"└── d (+)\n"
)
assert_print_statement(export.print_tree, expected_str, tree=tree_only_diff)

@staticmethod
def test_tree_diff_all_diff(tree_node):
other_tree_node = helper.prune_tree(tree_node, "a/c")
Expand Down

0 comments on commit cf32279

Please sign in to comment.