Coverage for pyTooling / Tree / __init__.py: 90%

371 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-07 17:18 +0000

1# ==================================================================================================================== # 

2# _____ _ _ _____ # 

3# _ __ _ |_ _|__ ___ | (_)_ __ __ _|_ _| __ ___ ___ # 

4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | | || '__/ _ \/ _ \ # 

5# | |_) | |_| || | (_) | (_) | | | | | | (_| |_| || | | __/ __/ # 

6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_||_| \___|\___| # 

7# |_| |___/ |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2017-2026 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31"""A powerful tree data structure for Python.""" 

32from collections import deque 

33from typing import TypeVar, Generic, List, Tuple, Dict, Deque, Union, Optional as Nullable 

34from typing import Callable, Iterator, Generator, Iterable, Mapping, Hashable 

35 

36from pyTooling.Decorators import export, readonly 

37from pyTooling.MetaClasses import ExtendedType 

38from pyTooling.Exceptions import ToolingException 

39from pyTooling.Common import getFullyQualifiedName 

40 

41 

42IDType = TypeVar("IDType", bound=Hashable) 

43"""A type variable for a tree's ID.""" 

44 

45ValueType = TypeVar("ValueType") 

46"""A type variable for a tree's value.""" 

47 

48DictKeyType = TypeVar("DictKeyType") 

49"""A type variable for a tree's dictionary keys.""" 

50 

51DictValueType = TypeVar("DictValueType") 

52"""A type variable for a tree's dictionary values.""" 

53 

54 

55@export 

56class TreeException(ToolingException): 

57 """Base exception of all exceptions raised by :mod:`pyTooling.Tree`.""" 

58 

59 

60@export 

61class InternalError(TreeException): 

62 """ 

63 The exception is raised when a data structure corruption is detected. 

64 

65 .. danger:: 

66 

67 This exception should never be raised. 

68 

69 If so, please create an issue at GitHub so the data structure corruption can be investigated and fixed. |br| 

70 `⇒ Bug Tracker at GitHub <https://github.com/pyTooling/pyTooling/issues>`__ 

71 """ 

72 

73 

74@export 

75class NoSiblingsError(TreeException): 

76 """ 

77 The exception is raised when a node has no parent and thus has no siblings. 

78 

79 .. hint:: 

80 

81 A node with no parent is the root node of the tree. 

82 """ 

83 

84 

85@export 

86class AlreadyInTreeError(TreeException): 

87 """ 

88 The exception is raised when the current node and the other node are already in the same tree. 

89 

90 .. hint:: 

91 

92 A tree a an acyclic graph without cross-edges. Thus backward edges and cross edges are permitted. 

93 """ 

94 

95 

96@export 

97class NotInSameTreeError(TreeException): 

98 """The exception is raised when the current node and the other node are not in the same tree.""" 

99 

100 

101@export 

102class Node(Generic[IDType, ValueType, DictKeyType, DictValueType], metaclass=ExtendedType, slots=True): 

103 """ 

104 A **tree** data structure can be constructed of ``Node`` instances. 

105 

106 Therefore, nodes can be connected to parent nodes or a parent node can add child nodes. This allows to construct a 

107 tree top-down or bottom-up. 

108 

109 .. hint:: 

110 

111 The top-down construction should be preferred, because it's slightly faster. 

112 

113 Each tree uses the **root** node (a.k.a. tree-representative) to store some per-tree data structures. E.g. a list of 

114 all IDs in a tree. For easy and quick access to such data structures, each sibling node contains a reference to the 

115 root node (:attr:`_root`). In case of adding a tree to an existing tree, such data structures get merged and all added 

116 nodes get assigned with new root references. Use the read-only property :attr:`Root` to access the root reference. 

117 

118 The reference to the parent node (:attr:`_parent`) can be access via property :attr:`Parent`. If the property's setter 

119 is used, a node and all its siblings are added to another tree or to a new position in the same tree. 

120 

121 The references to all node's children is stored in a list (:attr:`_children`). Children, siblings, ancestors, can be 

122 accessed via various generators: 

123 

124 * :meth:`GetAncestors` |rarr| iterate all ancestors bottom-up. 

125 * :meth:`GetChildren` |rarr| iterate all direct children. 

126 * :meth:`GetDescendants` |rarr| iterate all descendants. 

127 * :meth:`IterateLevelOrder` |rarr| IterateLevelOrder. 

128 * :meth:`IteratePreOrder` |rarr| iterate siblings in pre-order. 

129 * :meth:`IteratePostOrder` |rarr| iterate siblings in post-order. 

130 

131 Each node can have a **unique ID** or no ID at all (``nodeID=None``). The root node is used to store all IDs in a 

132 dictionary (:attr:`_nodesWithID`). In case no ID is given, all such ID-less nodes are collected in a single bin and store as a 

133 list of nodes. An ID can be modified after the Node was created. Use the read-only property :attr:`ID` to access 

134 the ID. 

135 

136 Each node can have a **value** (:attr:`_value`), which can be given at node creation time, or it can be assigned and/or 

137 modified later. Use the property :attr:`Value` to get or set the value. 

138 

139 Moreover, each node can store various key-value-pairs (:attr:`_dict`). Use the dictionary syntax to get and set 

140 key-value-pairs. 

141 """ 

142 

143 _id: Nullable[IDType] #: Unique identifier of a node. ``None`` if not used. 

144 _nodesWithID: Nullable[Dict[IDType, 'Node']] #: Dictionary of all IDs in the tree. ``None`` if it's not the root node. 

145 _nodesWithoutID: Nullable[List['Node']] #: List of all nodes without an ID in the tree. ``None`` if it's not the root node. 

146 _root: 'Node' #: Reference to the root of a tree. ``self`` if it's the root node. 

147 _parent: Nullable['Node'] #: Reference to the parent node. ``None`` if it's the root node. 

148 _children: List['Node'] #: List of all children 

149# _links: List['Node'] 

150 

151 _level: int #: Level of the node (distance to the root). 

152 _value: Nullable[ValueType] #: Field to store the node's value. 

153 _dict: Dict[DictKeyType, DictValueType] #: Dictionary to store key-value-pairs attached to the node. 

154 

155 _format: Nullable[Callable[["Node"], str]] #: A node formatting function returning a one-line representation for tree-rendering. 

156 

157 def __init__( 

158 self, 

159 nodeID: Nullable[IDType] = None, 

160 value: Nullable[ValueType] = None, 

161 keyValuePairs: Nullable[Mapping[DictKeyType, DictValueType]] = None, 

162 parent: 'Node' = None, 

163 children: Nullable[Iterable['Node']] = None, 

164 format: Nullable[Callable[["Node"], str]] = None 

165 ) -> None: 

166 """ 

167 .. todo:: TREE::Node::init Needs documentation. 

168 

169 :param nodeID: The optional unique ID of a node within the whole tree data structure. 

170 :param value: The optional value of the node. 

171 :param keyValuePairs: The optional mapping (dictionary) of key-value-pairs. 

172 :param parent: The optional parent node in the tree. 

173 :param children: The optional list of child nodes. 

174 :param format: The optional node formatting function returning a one-line representation for tree-rendering. 

175 

176 :raises TypeError: If parameter parent is not an instance of Node. 

177 :raises ValueError: If nodeID already exists in the tree. 

178 :raises TypeError: If parameter children is not iterable. 

179 :raises ValueError: If an element of children is not an instance of Node. 

180 """ 

181 

182 self._id = nodeID 

183 self._value = value 

184 self._dict = {key: value for key, value in keyValuePairs.items()} if keyValuePairs is not None else {} 

185 

186 self._format = format 

187 

188 if parent is not None and not isinstance(parent, Node): 

189 ex = TypeError("Parameter 'parent' is not of type 'Node'.") 

190 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

191 raise ex 

192 

193 if parent is None: 

194 self._root = self 

195 self._parent = None 

196 self._level = 0 

197 

198 self._nodesWithID = {} 

199 self._nodesWithoutID = [] 

200 if nodeID is None: 

201 self._nodesWithoutID.append(self) 

202 else: 

203 self._nodesWithID[nodeID] = self 

204 else: 

205 self._root = parent._root 

206 self._parent = parent 

207 self._level = parent._level + 1 

208 self._nodesWithID = None 

209 self._nodesWithoutID = None 

210 

211 if nodeID is None: 

212 self._root._nodesWithoutID.append(self) 

213 elif nodeID in self._root._nodesWithID: 

214 raise ValueError(f"ID '{nodeID}' already exists in this tree.") 

215 else: 

216 self._root._nodesWithID[nodeID] = self 

217 

218 parent._children.append(self) 

219 

220 self._children = [] 

221 if children is not None: 

222 if not isinstance(children, Iterable): 

223 ex = TypeError("Parameter 'children' is not iterable.") 

224 ex.add_note(f"Got type '{getFullyQualifiedName(children)}'.") 

225 raise ex 

226 

227 for child in children: 

228 if not isinstance(child, Node): 

229 ex = TypeError(f"Item '{child}' in parameter 'children' is not of type 'Node'.") 

230 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

231 raise ex 

232 

233 child.Parent = self 

234 

235 @readonly 

236 def ID(self) -> Nullable[IDType]: 

237 """ 

238 Read-only property to access the unique ID of a node (:attr:`_id`). 

239 

240 If no ID was given at node construction time, ID return None. 

241 

242 :returns: Unique ID of a node, if ID was given at node creation time, else None. 

243 """ 

244 return self._id 

245 

246 @property 

247 def Value(self) -> Nullable[ValueType]: 

248 """ 

249 Property to get and set the value (:attr:`_value`) of a node. 

250 

251 :returns: The value of a node. 

252 """ 

253 return self._value 

254 

255 @Value.setter 

256 def Value(self, value: Nullable[ValueType]) -> None: 

257 self._value = value 

258 

259 def __getitem__(self, key: DictKeyType) -> DictValueType: 

260 """ 

261 Read a node's attached attributes (key-value-pairs) by key. 

262 

263 :param key: The key to look for. 

264 :returns: The value associated to the given key. 

265 """ 

266 return self._dict[key] 

267 

268 def __setitem__(self, key: DictKeyType, value: DictValueType) -> None: 

269 """ 

270 Create or update a node's attached attributes (key-value-pairs) by key. 

271 

272 If a key doesn't exist yet, a new key-value-pair is created. 

273 

274 :param key: The key to create or update. 

275 :param value: The value to associate to the given key. 

276 """ 

277 self._dict[key] = value 

278 

279 def __delitem__(self, key: DictKeyType) -> None: 

280 """ 

281 .. todo:: TREE::Node::__delitem__ Needs documentation. 

282 

283 """ 

284 del self._dict[key] 

285 

286 def __contains__(self, key: DictKeyType) -> bool: 

287 """ 

288 .. todo:: TREE::Node::__contains__ Needs documentation. 

289 

290 """ 

291 return key in self._dict 

292 

293 def __len__(self) -> int: 

294 """ 

295 Returns the number of attached attributes (key-value-pairs) on this node. 

296 

297 :returns: Number of attached attributes. 

298 """ 

299 return len(self._dict) 

300 

301 @readonly 

302 def Root(self) -> 'Node': 

303 """ 

304 Read-only property to access the tree's root node (:attr:`_root`). 

305 

306 :returns: The root node (representative node) of a tree. 

307 """ 

308 return self._root 

309 

310 @property 

311 def Parent(self) -> Nullable['Node']: 

312 """ 

313 Property to get and set the parent (:attr:`_parent`) of a node. 

314 

315 .. note:: 

316 

317 As the current node might be a tree itself, appending this node to a tree can lead to a merge of trees and 

318 especially to a merge of IDs. As IDs are unique, it might raise an :exc:`Exception`. 

319 

320 :returns: The parent of a node. 

321 :raises TypeError: If parameter ``parent`` is not a :class:`Node` 

322 :raises AlreadyInTreeError: Parent is already a child node in this tree. 

323 """ 

324 return self._parent 

325 

326 @Parent.setter 

327 def Parent(self, parent: Nullable['Node']) -> None: 

328 # TODO: is moved inside the same tree, don't move nodes in _nodesWithID and don't change _root 

329 

330 if parent is None: 

331 self._nodesWithID = {} 

332 self._nodesWithoutID = [] 

333 self._level = 0 

334 

335 if self._id is None: 

336 self._nodesWithoutID.append(self) 

337 self._root._nodesWithoutID.remove(self) 

338 else: 

339 self._nodesWithID[self._id] = self 

340 del self._nodesWithID[self._id] 

341 

342 for sibling in self.GetDescendants(): 

343 sibling._root = self 

344 sibling._level = sibling._parent._level + 1 

345 if sibling._id is None: 

346 self._nodesWithoutID.append(sibling) 

347 self._root._nodesWithoutID.remove(sibling) 

348 else: 

349 self._nodesWithID[sibling._id] = sibling 

350 del self._nodesWithID[sibling._id] 

351 

352 self._parent._children.remove(self) 

353 

354 self._root = self 

355 self._parent = None 

356 elif not isinstance(parent, Node): 

357 ex = TypeError("Parameter 'parent' is not of type 'Node'.") 

358 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

359 raise ex 

360 else: 

361 if parent._root is self._root: 

362 raise AlreadyInTreeError(f"Parent '{parent}' is already a child node in this tree.") 

363 

364 self._root = parent._root 

365 self._parent = parent 

366 self._level = parent._level + 1 

367 for node in self.GetDescendants(): 

368 node._level = node._parent._level + 1 

369 self._SetNewRoot(self._nodesWithID, self._nodesWithoutID) 

370 self._nodesWithID = self._nodesWithoutID = None 

371 parent._children.append(self) 

372 

373 @readonly 

374 def Siblings(self) -> Tuple['Node', ...]: 

375 """ 

376 A read-only property to return a tuple of all siblings from the current node. 

377 

378 If the current node is the only child, the tuple is empty. 

379 

380 Siblings are child nodes of the current node's parent node, without the current node itself. 

381 

382 :returns: A tuple of all siblings of the current node. 

383 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

384 """ 

385 if self._parent is None: 

386 raise NoSiblingsError(f"Root node has no siblings.") 

387 

388 return tuple([node for node in self._parent if node is not self]) 

389 

390 @readonly 

391 def LeftSiblings(self) -> Tuple['Node', ...]: 

392 """ 

393 A read-only property to return a tuple of all siblings left from the current node. 

394 

395 If the current node is the only child, the tuple is empty. 

396 

397 Siblings are child nodes of the current node's parent node, without the current node itself. 

398 

399 :returns: A tuple of all siblings left of the current node. 

400 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

401 """ 

402 if self._parent is None: 

403 raise NoSiblingsError(f"Root node has no siblings.") 

404 

405 result = [] 

406 for node in self._parent: 

407 if node is not self: 

408 result.append(node) 

409 else: 

410 break 

411 else: 

412 raise InternalError(f"Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

413 

414 return tuple(result) 

415 

416 @readonly 

417 def RightSiblings(self) -> Tuple['Node', ...]: 

418 """ 

419 A read-only property to return a tuple of all siblings right from the current node. 

420 

421 If the current node is the only child, the tuple is empty. 

422 

423 Siblings are child nodes of the current node's parent node, without the current node itself. 

424 

425 :returns: A tuple of all siblings right of the current node. 

426 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

427 """ 

428 if self._parent is None: 

429 raise NoSiblingsError(f"Root node has no siblings.") 

430 

431 result = [] 

432 iterator = iter(self._parent) 

433 for node in iterator: 

434 if node is self: 

435 break 

436 else: 

437 raise InternalError(f"Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

438 

439 for node in iterator: 

440 result.append(node) 

441 

442 return tuple(result) 

443 

444 def _GetPathAsLinkedList(self) -> Deque["Node"]: 

445 """ 

446 Compute the path from current node to root node by using a linked list (:class:`deque`). 

447 

448 :meta private: 

449 :returns: Path from node to root node as double-ended queue (deque). 

450 """ 

451 path: Deque['Node'] = deque() 

452 

453 node = self 

454 while node is not None: 

455 path.appendleft(node) 

456 node = node._parent 

457 

458 return path 

459 

460 @readonly 

461 def Path(self) -> Tuple['Node']: 

462 """ 

463 Read-only property to return the path from root node to the node as a tuple of nodes. 

464 

465 :returns: A tuple of nodes describing the path from root node to the node. 

466 """ 

467 return tuple(self._GetPathAsLinkedList()) 

468 

469 @readonly 

470 def Level(self) -> int: 

471 """ 

472 Read-only property to return a node's level in the tree. 

473 

474 The level is the distance to the root node. 

475 

476 :returns: The node's level. 

477 """ 

478 return self._level 

479 

480 @readonly 

481 def Size(self) -> int: 

482 """ 

483 Read-only property to return the size of the tree. 

484 

485 :returns: Count of all nodes in the tree structure. 

486 """ 

487 return len(self._root._nodesWithID) + len(self._root._nodesWithoutID) 

488 

489 @readonly 

490 def IsRoot(self) -> bool: 

491 """ 

492 Returns true, if the node is the root node (representative node of the tree). 

493 

494 :returns: ``True``, if node is the root node. 

495 """ 

496 return self._parent is None 

497 

498 @readonly 

499 def IsLeaf(self) -> bool: 

500 """ 

501 Returns true, if the node is a leaf node (has no children). 

502 

503 :returns: ``True``, if node has no children. 

504 """ 

505 return len(self._children) == 0 

506 

507 @readonly 

508 def HasChildren(self) -> bool: 

509 """ 

510 Returns true, if the node has child nodes. 

511 

512 :returns: ``True``, if node has children. 

513 """ 

514 return len(self._children) > 0 

515 

516 def _SetNewRoot(self, nodesWithIDs: Dict['Node', 'Node'], nodesWithoutIDs: List['Node']) -> None: 

517 for nodeID, node in nodesWithIDs.items(): 

518 if nodeID in self._root._nodesWithID: 

519 raise ValueError(f"ID '{nodeID}' already exists in this tree.") 

520 else: 

521 self._root._nodesWithID[nodeID] = node 

522 node._root = self._root 

523 

524 for node in nodesWithoutIDs: 

525 self._root._nodesWithoutID.append(node) 

526 node._root = self._root 

527 

528 def AddChild(self, child: 'Node') -> None: 

529 """ 

530 Add a child node to the current node of the tree. 

531 

532 If ``child`` is a subtree, both trees get merged. So all nodes in ``child`` get a new :attr:`_root` assigned and 

533 all IDs are merged into the node's root's ID lists (:attr:`_nodesWithID`). 

534 

535 :param child: The child node to be added to the tree. 

536 :raises TypeError: If parameter ``child`` is not a :class:`Node`. 

537 :raises AlreadyInTreeError: If parameter ``child`` is already a node in the tree. 

538 

539 .. seealso:: 

540 

541 :attr:`Parent` |br| 

542 |rarr| Set the parent of a node. 

543 :meth:`AddChildren` |br| 

544 |rarr| Add multiple children at once. 

545 """ 

546 if not isinstance(child, Node): 

547 ex = TypeError(f"Parameter 'child' is not of type 'Node'.") 

548 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

549 raise ex 

550 

551 if child._root is self._root: 

552 raise AlreadyInTreeError(f"Child '{child}' is already a node in this tree.") 

553 

554 child._root = self._root 

555 child._parent = self 

556 child._level = self._level + 1 

557 for node in child.GetDescendants(): 

558 node._level = node._parent._level + 1 

559 self._SetNewRoot(child._nodesWithID, child._nodesWithoutID) 

560 child._nodesWithID = child._nodesWithoutID = None 

561 self._children.append(child) 

562 

563 def AddChildren(self, children: Iterable['Node']) -> None: 

564 """ 

565 Add multiple children nodes to the current node of the tree. 

566 

567 :param children: The list of children nodes to be added to the tree. 

568 :raises TypeError: If parameter ``children`` contains an item, which is not a :class:`Node`. 

569 :raises AlreadyInTreeError: If parameter ``children`` contains an item, which is already a node in the tree. 

570 

571 .. seealso:: 

572 

573 :attr:`Parent` |br| 

574 |rarr| Set the parent of a node. 

575 :meth:`AddChild` |br| 

576 |rarr| Add a child node to the tree. 

577 """ 

578 for child in children: 

579 if not isinstance(child, Node): 

580 ex = TypeError(f"Item '{child}' in parameter 'children' is not of type 'Node'.") 

581 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

582 raise ex 

583 

584 if child._root is self._root: 

585 # TODO: create a more specific exception 

586 raise AlreadyInTreeError(f"Child '{child}' is already a node in this tree.") 

587 

588 child._root = self._root 

589 child._parent = self 

590 child._level = self._level + 1 

591 for node in child.GetDescendants(): 

592 node._level = node._parent._level + 1 

593 self._SetNewRoot(child._nodesWithID, child._nodesWithoutID) 

594 child._nodesWithID = child._nodesWithoutID = None 

595 self._children.append(child) 

596 

597 def GetPath(self) -> Generator['Node', None, None]: 

598 """ 

599 .. todo:: TREE::Node::GetPAth Needs documentation. 

600 

601 """ 

602 for node in self._GetPathAsLinkedList(): 

603 yield node 

604 

605 def GetAncestors(self) -> Generator['Node', None, None]: 

606 """ 

607 .. todo:: TREE::Node::GetAncestors Needs documentation. 

608 

609 """ 

610 node = self._parent 

611 while node is not None: 

612 yield node 

613 node = node._parent 

614 

615 def GetCommonAncestors(self, others: Union['Node', Iterable['Node']]) -> Generator['Node', None, None]: 

616 """ 

617 .. todo:: TREE::Node::GetCommonAncestors Needs documentation. 

618 

619 """ 

620 if isinstance(others, Node): 

621 # Check for trivial case 

622 if others is self: 

623 for node in self._GetPathAsLinkedList(): 

624 yield node 

625 return 

626 

627 # Check if both are in the same tree. 

628 if self._root is not others._root: 

629 raise NotInSameTreeError(f"Node 'others' is not in the same tree.") 

630 

631 # Compute paths top-down and walk both paths until they deviate 

632 for left, right in zip(self.Path, others.Path): 

633 if left is right: 

634 yield left 

635 else: 

636 return 

637 elif isinstance(others, Iterable): 

638 raise NotImplementedError(f"Generator 'GetCommonAncestors' does not yet support an iterable of siblings to compute the common ancestors.") 

639 

640 def GetChildren(self) -> Generator['Node', None, None]: 

641 """ 

642 A generator to iterate all direct children of the current node. 

643 

644 :returns: A generator to iterate all children. 

645 

646 .. seealso:: 

647 

648 :meth:`GetDescendants` |br| 

649 |rarr| Iterate all descendants. 

650 :meth:`IterateLevelOrder` |br| 

651 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

652 :meth:`IteratePreOrder` |br| 

653 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

654 :meth:`IteratePostOrder` |br| 

655 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

656 """ 

657 for child in self._children: 

658 yield child 

659 

660 def GetSiblings(self) -> Generator['Node', None, None]: 

661 """ 

662 A generator to iterate all siblings. 

663 

664 Siblings are child nodes of the current node's parent node, without the current node itself. 

665 

666 :returns: A generator to iterate all siblings of the current node. 

667 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

668 """ 

669 if self._parent is None: 

670 raise NoSiblingsError(f"Root node has no siblings.") 

671 

672 for node in self._parent: 

673 if node is self: 

674 continue 

675 

676 yield node 

677 

678 def GetLeftSiblings(self) -> Generator['Node', None, None]: 

679 """ 

680 A generator to iterate all siblings left from the current node. 

681 

682 Siblings are child nodes of the current node's parent node, without the current node itself. 

683 

684 :returns: A generator to iterate all siblings left of the current node. 

685 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

686 """ 

687 if self._parent is None: 

688 raise NoSiblingsError(f"Root node has no siblings.") 

689 

690 for node in self._parent: 

691 if node is self: 

692 break 

693 

694 yield node 

695 else: 

696 raise InternalError(f"Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

697 

698 def GetRightSiblings(self) -> Generator['Node', None, None]: 

699 """ 

700 A generator to iterate all siblings right from the current node. 

701 

702 Siblings are child nodes of the current node's parent node, without the current node itself. 

703 

704 :returns: A generator to iterate all siblings right of the current node. 

705 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

706 """ 

707 if self._parent is None: 

708 raise NoSiblingsError(f"Root node has no siblings.") 

709 

710 iterator = iter(self._parent) 

711 for node in iterator: 

712 if node is self: 

713 break 

714 else: 

715 raise InternalError(f"Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

716 

717 for node in iterator: 

718 yield node 

719 

720 def GetDescendants(self) -> Generator['Node', None, None]: 

721 """ 

722 A generator to iterate all descendants of the current node. In contrast to `IteratePreOrder` and `IteratePostOrder` 

723 it doesn't include the node itself. 

724 

725 :returns: A generator to iterate all descendants. 

726 

727 .. seealso:: 

728 

729 :meth:`GetChildren` |br| 

730 |rarr| Iterate all children, but no grand-children. 

731 :meth:`IterateLevelOrder` |br| 

732 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

733 :meth:`IteratePreOrder` |br| 

734 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

735 :meth:`IteratePostOrder` |br| 

736 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

737 """ 

738 for child in self._children: 

739 yield child 

740 yield from child.GetDescendants() 

741 

742 def GetRelatives(self) -> Generator['Node', None, None]: 

743 """ 

744 A generator to iterate all relatives (all siblings and all their descendants) of the current node. 

745 

746 :returns: A generator to iterate all relatives. 

747 """ 

748 for node in self.GetSiblings(): 

749 yield node 

750 yield from node.GetDescendants() 

751 

752 def GetLeftRelatives(self) -> Generator['Node', None, None]: 

753 """ 

754 A generator to iterate all left relatives (left siblings and all their descendants) of the current node. 

755 

756 :returns: A generator to iterate all left relatives. 

757 """ 

758 for node in self.GetLeftSiblings(): 

759 yield node 

760 yield from node.GetDescendants() 

761 

762 def GetRightRelatives(self) -> Generator['Node', None, None]: 

763 """ 

764 A generator to iterate all right relatives (right siblings and all their descendants) of the current node. 

765 

766 :returns: A generator to iterate all right relatives. 

767 """ 

768 for node in self.GetRightSiblings(): 

769 yield node 

770 yield from node.GetDescendants() 

771 

772 def IterateLeafs(self) -> Generator['Node', None, None]: 

773 """ 

774 A generator to iterate all leaf-nodes in a subtree, which subtree root is the current node. 

775 

776 :returns: A generator to iterate leaf-nodes reachable from current node. 

777 """ 

778 for child in self._children: 

779 if child.IsLeaf: 

780 yield child 

781 else: 

782 yield from child.IterateLeafs() 

783 

784 def IterateLevelOrder(self) -> Generator['Node', None, None]: 

785 """ 

786 A generator to iterate all siblings of the current node level-by-level top-down. In contrast to `GetDescendants`, 

787 this includes also the node itself as the first returned node. 

788 

789 :returns: A generator to iterate all siblings level-by-level. 

790 

791 .. seealso:: 

792 

793 :meth:`GetChildren` |br| 

794 |rarr| Iterate all children, but no grand-children. 

795 :meth:`GetDescendants` |br| 

796 |rarr| Iterate all descendants. 

797 :meth:`IteratePreOrder` |br| 

798 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

799 :meth:`IteratePostOrder` |br| 

800 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

801 """ 

802 queue = deque([self]) 

803 while queue: 

804 currentNode = queue.pop() 

805 yield currentNode 

806 for node in currentNode._children: 

807 queue.appendleft(node) 

808 

809 def IteratePreOrder(self) -> Generator['Node', None, None]: 

810 """ 

811 A generator to iterate all siblings of the current node in pre-order. In contrast to `GetDescendants`, this includes 

812 also the node itself as the first returned node. 

813 

814 :returns: A generator to iterate all siblings in pre-order. 

815 

816 .. seealso:: 

817 

818 :meth:`GetChildren` |br| 

819 |rarr| Iterate all children, but no grand-children. 

820 :meth:`GetDescendants` |br| 

821 |rarr| Iterate all descendants. 

822 :meth:`IterateLevelOrder` |br| 

823 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

824 :meth:`IteratePostOrder` |br| 

825 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

826 """ 

827 yield self 

828 for child in self._children: 

829 yield from child.IteratePreOrder() 

830 

831 def IteratePostOrder(self) -> Generator['Node', None, None]: 

832 """ 

833 A generator to iterate all siblings of the current node in post-order. In contrast to `GetDescendants`, this 

834 includes also the node itself as the last returned node. 

835 

836 :returns: A generator to iterate all siblings in post-order. 

837 

838 .. seealso:: 

839 

840 :meth:`GetChildren` |br| 

841 |rarr| Iterate all children, but no grand-children. 

842 :meth:`GetDescendants` |br| 

843 |rarr| Iterate all descendants. 

844 :meth:`IterateLevelOrder` |br| 

845 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

846 :meth:`IteratePreOrder` |br| 

847 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

848 """ 

849 for child in self._children: 

850 yield from child.IteratePostOrder() 

851 yield self 

852 

853 def WalkTo(self, other: 'Node') -> Generator['Node', None, None]: 

854 """ 

855 Returns a generator to iterate the path from node to another node. 

856 

857 :param other: Node to walk to. 

858 :returns: Generator to iterate the path from node to other node. 

859 :raises NotInSameTreeError: If parameter ``other`` is not part of the same tree. 

860 """ 

861 # Check for trivial case 

862 if other is self: 

863 yield from () 

864 

865 # Check if both are in the same tree. 

866 if self._root is not other._root: 

867 raise NotInSameTreeError(f"Node 'other' is not in the same tree.") 

868 

869 # Compute both paths to the root. 

870 # 1. Walk from self to root, until a first common ancestor is found. 

871 # 2. Walk from there to other (reverse paths) 

872 otherPath = other.Path # TODO: Path generates a list and a tuple. Provide a generator for such a walk. 

873 index = len(otherPath) 

874 for node in self.GetAncestors(): 

875 try: 

876 index = otherPath.index(node) 

877 break 

878 except ValueError: 

879 yield node 

880 

881 for i in range(index, len(otherPath)): 

882 yield otherPath[i] 

883 

884 def GetNodeByID(self, nodeID: IDType) -> 'Node': 

885 """ 

886 Lookup a node by its unique ID. 

887 

888 :param nodeID: ID of a node to lookup in the tree. 

889 :returns: Node for the given ID. 

890 :raises ValueError: If parameter ``nodeID`` is None. 

891 :raises KeyError: If parameter ``nodeID`` is not found in the tree. 

892 """ 

893 if nodeID is None: 

894 raise ValueError(f"'None' is not supported as an ID value.") 

895 

896 return self._root._nodesWithID[nodeID] 

897 

898 def Find(self, predicate: Callable) -> Generator['Node', None, None]: 

899 raise NotImplementedError(f"Method 'Find' is not yet implemented.") 

900 

901 def __iter__(self) -> Iterator['Node']: 

902 """ 

903 Returns an iterator to iterate all child nodes. 

904 

905 :returns: Children iterator. 

906 """ 

907 return iter(self._children) 

908 

909 def __len__(self) -> int: 

910 """ 

911 Returns the number of children, but not including grand-children. 

912 

913 :returns: Number of child nodes. 

914 """ 

915 return len(self._children) 

916 

917 def __repr__(self) -> str: 

918 """ 

919 Returns a detailed string representation of the node. 

920 

921 :returns: The detailed string representation of the node. 

922 """ 

923 nodeID = parent = value = "" 

924 if self._id is not None: 

925 nodeID = f"; nodeID='{self._id}'" 

926 if (self._parent is not None) and (self._parent._id is not None): 

927 parent = f"; parent='{self._parent._id}'" 

928 if self._value is not None: 

929 value = f"; value='{self._value}'" 

930 

931 return f"<node{nodeID}{parent}{value}>" 

932 

933 def __str__(self) -> str: 

934 """ 

935 Return a string representation of the node. 

936 

937 Order of resolution: 

938 

939 1. If :attr:`_value` is not None, return the string representation of :attr:`_value`. 

940 2. If :attr:`_id` is not None, return the string representation of :attr:`_id`. 

941 3. Else, return :meth:`__repr__`. 

942 

943 :returns: The resolved string representation of the node. 

944 """ 

945 if self._value is not None: 

946 return str(self._value) 

947 elif self._id is not None: 

948 return str(self._id) 

949 else: 

950 return self.__repr__() 

951 

952 def Render( 

953 self, 

954 prefix: str = "", 

955 lineend: str = "\n", 

956 nodeMarker: str = "├─", 

957 lastNodeMarker: str = "└─", 

958 bypassMarker: str = "│ " 

959 ) -> str: 

960 """ 

961 Render the tree as ASCII art. 

962 

963 :param prefix: A string printed in front of every line, e.g. for indentation. Default: ``""``. 

964 :param lineend: A string printed at the end of every line. Default: ``"\\n"``. 

965 :param nodeMarker: A string printed before every non-last tree node. Default: ``"├─"``. 

966 :param lastNodeMarker: A string printed before every last tree node. Default: ``"└─"``. 

967 :param bypassMarker: A string printed when there are further nodes in the parent level. Default: ``"│ "``. 

968 :return: A rendered tree as multiline string. 

969 """ 

970 emptyMarker = " " * len(bypassMarker) 

971 

972 def _render(node: Node, markers: str): 

973 result = [] 

974 

975 if node.HasChildren: 

976 for child in node._children[:-1]: 

977 nodeRepresentation = child._format(child) if child._format else str(child) 

978 result.append(f"{prefix}{markers}{nodeMarker}{nodeRepresentation}{lineend}") 

979 result.extend(_render(child, markers + bypassMarker)) 

980 

981 # last child node 

982 child = node._children[-1] 

983 nodeRepresentation = child._format(child) if child._format else str(child) 

984 result.append(f"{prefix}{markers}{lastNodeMarker}{nodeRepresentation}{lineend}") 

985 result.extend(_render(child, markers + emptyMarker)) 

986 

987 return result 

988 

989 # Root element 

990 nodeRepresentation = self._format(self) if self._format else str(self) 

991 result = [f"{prefix}{nodeRepresentation}{lineend}"] 

992 result.extend(_render(self, "")) 

993 

994 return "".join(result)