0
# Tree Modification Operations
1
2
Comprehensive functionality for dynamically modifying tree structures including moving nodes, removing branches, copying subtrees, and updating node properties while maintaining data integrity and relationships.
3
4
## Capabilities
5
6
### Node Movement and Reorganization
7
8
Move nodes within the tree to reorganize structure while preserving subtree relationships.
9
10
```python { .api }
11
def move_node(source, destination) -> None:
12
"""
13
Move a node and its subtree to a new parent.
14
15
Parameters:
16
- source: str, identifier of node to move
17
- destination: str, identifier of new parent node
18
19
Raises:
20
LoopError if move would create circular reference
21
NodeIDAbsentError if source or destination doesn't exist
22
"""
23
```
24
25
**Usage Examples:**
26
27
```python
28
# Move employee to different department
29
tree.move_node("alice", "sales_dept")
30
31
# Reorganize departments
32
tree.move_node("engineering", "technology_division")
33
34
# Move multiple nodes
35
employees_to_transfer = ["bob", "carol", "dave"]
36
for emp_id in employees_to_transfer:
37
tree.move_node(emp_id, "new_department")
38
39
# Move with error handling
40
try:
41
tree.move_node("manager", "subordinate") # Would create loop
42
except LoopError:
43
print("Cannot move manager under subordinate")
44
```
45
46
### Node and Subtree Removal
47
48
Remove individual nodes or entire subtrees from the tree structure.
49
50
```python { .api }
51
def remove_node(identifier) -> int:
52
"""
53
Remove a node and all its descendants from the tree.
54
55
Parameters:
56
- identifier: str, node identifier to remove
57
58
Returns:
59
int, number of nodes removed (including descendants)
60
61
Raises:
62
NodeIDAbsentError if node doesn't exist
63
"""
64
65
def link_past_node(nid) -> None:
66
"""
67
Remove node while connecting its children to its parent.
68
69
Parameters:
70
- nid: str, node identifier to remove
71
72
Raises:
73
LinkPastRootNodeError if attempting to link past root
74
NodeIDAbsentError if node doesn't exist
75
"""
76
```
77
78
**Usage Examples:**
79
80
```python
81
# Remove employee and report structure
82
removed_count = tree.remove_node("manager_leaving")
83
print(f"Removed {removed_count} nodes from organization")
84
85
# Remove department but keep employees
86
tree.link_past_node("temp_department") # Employees move up to parent
87
88
# Conditional removal
89
nodes_to_remove = []
90
for node_id in tree.expand_tree():
91
node = tree[node_id]
92
if node.data and node.data.get("status") == "inactive":
93
nodes_to_remove.append(node_id)
94
95
for node_id in nodes_to_remove:
96
tree.remove_node(node_id)
97
98
# Safe removal with validation
99
if "old_project" in tree:
100
if not tree.children("old_project"): # Check if has children
101
tree.remove_node("old_project")
102
else:
103
print("Cannot remove project with active sub-projects")
104
```
105
106
### Node Property Updates
107
108
Update node attributes and data while maintaining tree structure.
109
110
```python { .api }
111
def update_node(nid, **attrs) -> None:
112
"""
113
Update node attributes.
114
115
Parameters:
116
- nid: str, node identifier
117
- **attrs: keyword arguments for node attributes (tag, data, etc.)
118
119
Raises:
120
NodeIDAbsentError if node doesn't exist
121
"""
122
```
123
124
**Usage Examples:**
125
126
```python
127
# Update node tag (display name)
128
tree.update_node("emp_001", tag="Alice Smith (Senior Engineer)")
129
130
# Update node data
131
tree.update_node("emp_001", data={
132
"name": "Alice Smith",
133
"role": "Senior Engineer",
134
"salary": 125000,
135
"department": "Engineering"
136
})
137
138
# Multiple attribute update
139
tree.update_node("project_x",
140
tag="Project X - Phase 2",
141
data={"status": "active", "budget": 500000})
142
143
# Conditional updates
144
for node_id in tree.expand_tree():
145
node = tree[node_id]
146
if node.data and node.data.get("role") == "intern":
147
tree.update_node(node_id,
148
tag=f"{node.tag} (Full-time)",
149
data={**node.data, "role": "junior"})
150
```
151
152
### Subtree Operations
153
154
Create, extract, and manipulate subtrees as independent tree structures.
155
156
```python { .api }
157
def subtree(nid, identifier=None) -> Tree:
158
"""
159
Create a shallow copy of subtree rooted at specified node.
160
161
Parameters:
162
- nid: str, root node identifier for subtree
163
- identifier: str, identifier for new tree (optional)
164
165
Returns:
166
Tree, new tree containing the subtree
167
168
Raises:
169
NodeIDAbsentError if node doesn't exist
170
"""
171
172
def remove_subtree(nid, identifier=None) -> Tree:
173
"""
174
Extract subtree from tree (removes from original).
175
176
Parameters:
177
- nid: str, root node identifier for subtree
178
- identifier: str, identifier for extracted tree (optional)
179
180
Returns:
181
Tree, extracted subtree as independent tree
182
"""
183
```
184
185
**Usage Examples:**
186
187
```python
188
# Create copy of department subtree
189
eng_dept_copy = tree.subtree("engineering")
190
eng_dept_copy.show()
191
192
# Extract department for restructuring
193
sales_dept = tree.remove_subtree("sales")
194
print(f"Extracted {sales_dept.size()} person sales department")
195
196
# Process subtrees independently
197
all_departments = ["engineering", "sales", "hr"]
198
dept_trees = {}
199
for dept_id in all_departments:
200
if dept_id in tree:
201
dept_trees[dept_id] = tree.subtree(dept_id)
202
203
# Analyze subtrees
204
for dept_name, dept_tree in dept_trees.items():
205
employee_count = dept_tree.size() - 1 # Exclude department head
206
print(f"{dept_name}: {employee_count} employees")
207
```
208
209
### Tree Merging and Pasting
210
211
Combine trees and paste subtrees to build complex structures.
212
213
```python { .api }
214
def paste(nid, new_tree, deep=False) -> None:
215
"""
216
Paste another tree as subtree under specified node.
217
218
Parameters:
219
- nid: str, parent node identifier where tree will be pasted
220
- new_tree: Tree, tree to paste as subtree
221
- deep: bool, perform deep copy if True
222
223
Raises:
224
NodeIDAbsentError if parent node doesn't exist
225
"""
226
227
def merge(nid, new_tree, deep=False) -> None:
228
"""
229
Merge another tree's children under specified node.
230
231
Parameters:
232
- nid: str, parent node identifier
233
- new_tree: Tree, tree whose children will be merged
234
- deep: bool, perform deep copy if True
235
"""
236
```
237
238
**Usage Examples:**
239
240
```python
241
# Create new subsidiary structure
242
subsidiary = Tree()
243
subsidiary.create_node("Subsidiary Corp", "sub_corp")
244
subsidiary.create_node("Sub Engineering", "sub_eng", parent="sub_corp")
245
subsidiary.create_node("Sub Sales", "sub_sales", parent="sub_corp")
246
247
# Paste entire subsidiary under main company
248
tree.paste("company", subsidiary)
249
250
# Merge departments from acquired company
251
acquired_company = Tree()
252
acquired_company.create_node("Acquired Corp", "acq_corp")
253
acquired_company.create_node("R&D Department", "rd", parent="acq_corp")
254
acquired_company.create_node("Marketing", "marketing", parent="acq_corp")
255
256
# Merge just the departments (not the company node)
257
tree.merge("company", acquired_company)
258
259
# Deep copy for independent data
260
secure_backup = tree.subtree("sensitive_project")
261
tree.paste("backup_location", secure_backup, deep=True)
262
```
263
264
### Advanced Modification Patterns
265
266
Complex modification scenarios and bulk operations.
267
268
**Usage Examples:**
269
270
```python
271
# Bulk reorganization
272
def reorganize_by_role():
273
# Group employees by role
274
roles = {}
275
for node_id in tree.expand_tree():
276
node = tree[node_id]
277
if node.data and "role" in node.data:
278
role = node.data["role"]
279
if role not in roles:
280
roles[role] = []
281
roles[role].append(node_id)
282
283
# Create role-based departments
284
for role, employee_ids in roles.items():
285
dept_id = f"{role}_dept"
286
tree.create_node(f"{role.title()} Department", dept_id, parent="company")
287
for emp_id in employee_ids:
288
tree.move_node(emp_id, dept_id)
289
290
# Conditional restructuring
291
def flatten_single_child_branches():
292
"""Remove nodes that have only one child by linking past them."""
293
nodes_to_remove = []
294
for node_id in tree.expand_tree():
295
children = tree.children(node_id)
296
if len(children) == 1 and not tree.is_root(node_id):
297
nodes_to_remove.append(node_id)
298
299
for node_id in nodes_to_remove:
300
tree.link_past_node(node_id)
301
302
# Tree surgery - replace subtree
303
def replace_subtree(old_root_id, new_subtree):
304
"""Replace an existing subtree with a new one."""
305
parent_id = tree.parent(old_root_id).identifier if tree.parent(old_root_id) else None
306
tree.remove_node(old_root_id)
307
if parent_id:
308
tree.paste(parent_id, new_subtree)
309
else:
310
# Replacing root - more complex operation
311
tree.nodes.clear()
312
tree.paste(None, new_subtree)
313
314
# Batch updates with transaction-like behavior
315
def batch_update(updates):
316
"""Apply multiple updates with rollback on error."""
317
backup = Tree(tree, deep=True) # Create backup
318
try:
319
for update_type, *args in updates:
320
if update_type == "move":
321
tree.move_node(*args)
322
elif update_type == "remove":
323
tree.remove_node(*args)
324
elif update_type == "update":
325
tree.update_node(*args)
326
except Exception as e:
327
# Rollback on error
328
tree.nodes = backup.nodes
329
tree.root = backup.root
330
raise e
331
332
# Usage of batch update
333
updates = [
334
("move", "alice", "new_dept"),
335
("update", "bob", {"role": "senior"}),
336
("remove", "old_project")
337
]
338
batch_update(updates)
339
```
340
341
### Tree Structure Validation
342
343
Validate tree integrity after modifications.
344
345
**Usage Examples:**
346
347
```python
348
def validate_tree_integrity():
349
"""Validate tree structure after modifications."""
350
issues = []
351
352
# Check for orphaned nodes
353
for node_id, node in tree.nodes.items():
354
if node_id != tree.root:
355
parent_id = tree.parent(node_id)
356
if not parent_id or parent_id.identifier not in tree.nodes:
357
issues.append(f"Orphaned node: {node_id}")
358
359
# Check for circular references
360
visited = set()
361
def check_cycles(node_id, path):
362
if node_id in path:
363
issues.append(f"Circular reference: {path + [node_id]}")
364
return
365
if node_id in visited:
366
return
367
visited.add(node_id)
368
for child in tree.children(node_id):
369
check_cycles(child.identifier, path + [node_id])
370
371
if tree.root:
372
check_cycles(tree.root, [])
373
374
return issues
375
376
# Validate after major changes
377
issues = validate_tree_integrity()
378
if issues:
379
print("Tree integrity issues found:")
380
for issue in issues:
381
print(f" - {issue}")
382
```
383
384
## Exception Handling
385
386
```python { .api }
387
class LoopError(Exception):
388
"""Raised when operation would create circular reference"""
389
390
class LinkPastRootNodeError(Exception):
391
"""Raised when attempting to link past root node"""
392
393
class NodeIDAbsentError(Exception):
394
"""Raised when referencing non-existent node"""
395
```
396
397
**Usage Examples:**
398
399
```python
400
from treelib import LoopError, LinkPastRootNodeError, NodeIDAbsentError
401
402
# Safe node movement
403
def safe_move_node(source, destination):
404
try:
405
tree.move_node(source, destination)
406
return True
407
except LoopError:
408
print(f"Cannot move {source} to {destination}: would create loop")
409
return False
410
except NodeIDAbsentError as e:
411
print(f"Node not found: {e}")
412
return False
413
414
# Safe link past operation
415
def safe_link_past(node_id):
416
try:
417
tree.link_past_node(node_id)
418
return True
419
except LinkPastRootNodeError:
420
print("Cannot link past root node")
421
return False
422
except NodeIDAbsentError:
423
print(f"Node {node_id} not found")
424
return False
425
```