diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e21d88..f128ec35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,18 @@ 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). ## [Unreleased] + +## [0.23.0] - 2024-12-26 ### Changed: - Tree Modify: Update documentation and docstring with some rephrasing. - Tree Modify: Clean up test cases. ### Added: - Tree Modify: Add parameter `merge_attribute` to allow from-node and to-node attributes to be merged if there are clashes. ### Fixed: -- Tree Modify: Fixed bug when `merge_children` is used with `overriding` as the `merge_children` value is changed in for-loop (bad move, literally). +- Tree Modify: Fixed bug when `merge_children` is used with `overriding` as the `merge_children` value is changed in +for-loop (bad move, literally). Modified the logic such that if there are clashes for `merge_children=True, overriding=True`, +the origin node parent and destination node children are preserved. The origin node's children are overridden. +**This might not be backwards-compatible!** ## [0.22.3] - 2024-11-14 ### Added: diff --git a/bigtree/__init__.py b/bigtree/__init__.py index 0be8dbb2..239e4a3c 100644 --- a/bigtree/__init__.py +++ b/bigtree/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.22.3" +__version__ = "0.23.0" from bigtree.binarytree.construct import list_to_binarytree from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag diff --git a/bigtree/tree/modify.py b/bigtree/tree/modify.py index e9bad59d..212a51c8 100644 --- a/bigtree/tree/modify.py +++ b/bigtree/tree/modify.py @@ -1216,10 +1216,6 @@ def copy_or_shift_logic( # Perform shifting/copying for from_path, to_path in zip(from_paths, to_paths): - # Reset parameters - merge_children2 = merge_children - merge_leaves2 = merge_leaves - if with_full_path: from_node = search.find_full_path(tree, from_path) else: @@ -1264,10 +1260,7 @@ def copy_or_shift_logic( logging.info( f"Path {to_path} already exists and its children be overridden by the merge" ) - parent = to_node.parent - to_node.parent = None - to_node = parent - merge_children2 = False + del to_node.children elif merge_attribute: logging.info( f"Path {to_path} already exists and their attributes will be merged" @@ -1279,10 +1272,10 @@ def copy_or_shift_logic( merge_children=merge_children, merge_leaves=merge_leaves, ) - parent = to_node.parent - to_node.parent = None - to_node = parent - merge_children2 = False + to_node.set_attrs( + dict(from_node.describe(exclude_prefix="_")) + ) + del to_node.children else: logging.info( f"Path {to_path} already exists and children are merged" @@ -1304,10 +1297,10 @@ def copy_or_shift_logic( merge_children=merge_children, merge_leaves=merge_leaves, ) - parent = to_node.parent - to_node.parent = None - to_node = parent - merge_leaves2 = False + to_node.set_attrs( + dict(from_node.describe(exclude_prefix="_")) + ) + del to_node.children else: logging.info( f"Path {to_path} already exists and leaves are merged" @@ -1353,7 +1346,7 @@ def copy_or_shift_logic( if copy: logging.debug(f"Copying {from_node.node_name}") from_node = from_node.copy() - if merge_children2: + if merge_children: # overriding / merge_attribute handled merge_children, set merge_children=False logging.debug( f"Reassigning children from {from_node.node_name} to {to_node.node_name}" @@ -1363,7 +1356,7 @@ def copy_or_shift_logic( del children.children children.parent = to_node from_node.parent = None - elif merge_leaves2: + elif merge_leaves: logging.debug( f"Reassigning leaf nodes from {from_node.node_name} to {to_node.node_name}" ) diff --git a/docs/bigtree/tree/modify.md b/docs/bigtree/tree/modify.md index b158788b..c8a67d06 100644 --- a/docs/bigtree/tree/modify.md +++ b/docs/bigtree/tree/modify.md @@ -94,33 +94,37 @@ If you're still feeling lost over the parameters, here are some guiding question - Do I want to retain the original node where they are? - Yes: Set `copy=True` - Default performs a shift instead of copy -- Am I unsure of what nodes I am going to shift, they may or may not exist and this is perfectly fine? +- Am I unsure of what nodes I am going to copy/shift, they may or may not exist and this is perfectly fine? - Yes: Set `skippable=True` - Default throws error if origin node is not found - The origin node (and its descendants) may clash with the destination node(s), how do I want to handle it? - Set `overriding=True` to overwrite origin node - Set `merge_attribute=True` to combine both nodes' attributes - Default throws error about the clash in node name -- I want to shift everything under the node, but not the node itself +- I want to copy/shift everything under the node, but not the node itself - Set `merge_children=True` or `merge_leaves=True` to shift the children and leaf nodes respectively - Default shifts the node itself, and everything under it -- I want to shift the node and only the node, and not everything under it +- I want to copy/shift the node and only the node, and not everything under it - Yes: Set `delete_children=True` - Default shifts the node itself, and everything under it -- I want to shift things from one tree to another tree +- I want to copy/shift things from one tree to another tree - Specify `to_tree` - Default shifts nodes within the same tree What about the permutations between the parameters? -- These parameters are standalone and does not produce any interaction effect +- These parameters are standalone and do not produce any interaction effect - `copy`, `skippable`, `delete_children` - These parameters have some interaction: - `overriding` and `merge_attribute` with `merge_children` and `merge_leaves` - - `overriding` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `overriding`. Note that clashes will preserve destination nodes' children only. - - `overriding` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `overriding`. Note that clashes will preserve destination nodes' leaves only. - - `merge_attribute` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `merge_attribute`. Note that attributes will be merged for node and all descendants, and will preserve origin and destination nodes' children. - - `merge_attribute` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `merge_attribute`. Note that attributes will be merged for node and all descendants, and will preserve origin nodes' children and destination nodes' leaves. + - `overriding` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `overriding`. + Note that clashes will preserve origin node parent and destination nodes' children. + - `overriding` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `overriding`. + Note that clashes will preserve origin node parent and destination nodes' leaves. + - `merge_attribute` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `merge_attribute`. + Note that attributes will be merged for node and all descendants, and will preserve origin and destination nodes' children. + - `merge_attribute` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `merge_attribute`. + Note that attributes will be merged for node and all descendants, and will preserve origin nodes' children and destination nodes' leaves. ----- diff --git a/docs/gettingstarted/demo/tree.md b/docs/gettingstarted/demo/tree.md index 38981500..93a84533 100644 --- a/docs/gettingstarted/demo/tree.md +++ b/docs/gettingstarted/demo/tree.md @@ -617,6 +617,9 @@ Nodes can be shifted (with or without replacement) or copied< from one path to another, this changes the tree in-place. Nodes can also be copied (with or without replacement) between two different trees. +There are various other configurations for performing copying/shifting, refer to [code documentation](../../bigtree/tree/modify.md) +for more examples. + === "Shift nodes" ```python hl_lines="12-16 24-28" from bigtree import list_to_tree, shift_nodes, shift_and_replace_nodes diff --git a/tests/tree/test_modify.py b/tests/tree/test_modify.py index 9216effd..0c95e167 100644 --- a/tests/tree/test_modify.py +++ b/tests/tree/test_modify.py @@ -2377,7 +2377,7 @@ def test_copy_nodes_from_tree_to_tree_merge_children_overriding(self): overriding=True, ) assert_tree_structure_basenode_root(self.root_other_full_wrong) - assert_tree_structure_basenode_root_attr(self.root_other_full_wrong) + assert_tree_structure_basenode_root_attr(self.root_other_full_wrong, c=("c", 1)) assert_tree_structure_node_root(self.root_other_full_wrong) # merge_children, merge_attribute @@ -2454,6 +2454,7 @@ def test_copy_nodes_from_tree_to_tree_merge_leaves_merge_attribute(self): merge_leaves=True, merge_attribute=True, ) + self.root_other_full_wrong["b"].sort(key=lambda node: node.node_name) assert_tree_structure_basenode_root(self.root_other_full_wrong) assert_tree_structure_basenode_root_attr(self.root_other_full_wrong) assert_tree_structure_node_root(self.root_other_full_wrong)