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

366 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-25 22:22 +0000

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

2# _____ _ _ _____ # 

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

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

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

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

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

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2017-2025 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 sys import version_info # needed for versions before Python 3.11 

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

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

36 

37try: 

38 from pyTooling.Decorators import export, readonly 

39 from pyTooling.MetaClasses import ExtendedType 

40 from pyTooling.Exceptions import ToolingException 

41 from pyTooling.Common import getFullyQualifiedName 

42except (ImportError, ModuleNotFoundError): # pragma: no cover 

43 print("[pyTooling.Tree] Could not import from 'pyTooling.*'!") 

44 

45 try: 

46 from Decorators import export, readonly 

47 from MetaClasses import ExtendedType, mixin 

48 from Exceptions import ToolingException 

49 from Common import getFullyQualifiedName 

50 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover 

51 print("[pyTooling.Tree] Could not import directly!") 

52 raise ex 

53 

54 

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

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

57 

58ValueType = TypeVar("ValueType") 

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

60 

61DictKeyType = TypeVar("DictKeyType") 

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

63 

64DictValueType = TypeVar("DictValueType") 

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

66 

67 

68@export 

69class TreeException(ToolingException): 

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

71 

72 

73@export 

74class InternalError(TreeException): 

75 """ 

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

77 

78 .. danger:: 

79 

80 This exception should never be raised. 

81 

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

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

84 """ 

85 

86 

87@export 

88class NoSiblingsError(TreeException): 

89 """ 

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

91 

92 .. hint:: 

93 

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

95 """ 

96 

97 

98@export 

99class AlreadyInTreeError(TreeException): 

100 """ 

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

102 

103 .. hint:: 

104 

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

106 """ 

107 

108 

109@export 

110class NotInSameTreeError(TreeException): 

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

112 

113 

114@export 

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

116 """ 

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

118 

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

120 tree top-down or bottom-up. 

121 

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

123 

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

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

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

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

128 

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

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

131 

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

133 accessed via various generators: 

134 

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

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

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

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

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

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

141 

142 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 

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

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

145 the ID. 

146 

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

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

149 

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

151 key-value-pairs. 

152 """ 

153 

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

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

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

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

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

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

160# _links: List['Node'] 

161 

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

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

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

165 

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

167 

168 def __init__( 

169 self, 

170 nodeID: Nullable[IDType] = None, 

171 value: Nullable[ValueType] = None, 

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

173 parent: 'Node' = None, 

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

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

176 ) -> None: 

177 """ 

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

179 

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

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

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

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

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

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

186 

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

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

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

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

191 """ 

192 

193 self._id = nodeID 

194 self._value = value 

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

196 

197 self._format = format 

198 

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

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

201 if version_info >= (3, 11): # pragma: no cover 

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

203 raise ex 

204 

205 if parent is None: 

206 self._root = self 

207 self._parent = None 

208 self._level = 0 

209 

210 self._nodesWithID = {} 

211 self._nodesWithoutID = [] 

212 if nodeID is None: 

213 self._nodesWithoutID.append(self) 

214 else: 

215 self._nodesWithID[nodeID] = self 

216 else: 

217 self._root = parent._root 

218 self._parent = parent 

219 self._level = parent._level + 1 

220 self._nodesWithID = None 

221 

222 if nodeID is None: 

223 self._root._nodesWithoutID.append(self) 

224 elif nodeID in self._root._nodesWithID: 

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

226 else: 

227 self._root._nodesWithID[nodeID] = self 

228 

229 parent._children.append(self) 

230 

231 self._children = [] 

232 if children is not None: 

233 if not isinstance(children, Iterable): 

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

235 if version_info >= (3, 11): # pragma: no cover 

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

237 raise ex 

238 

239 for child in children: 

240 if not isinstance(child, Node): 

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

242 if version_info >= (3, 11): # pragma: no cover 

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

244 raise ex 

245 

246 child.Parent = self 

247 

248 @readonly 

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

250 """ 

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

252 

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

254 

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

256 """ 

257 return self._id 

258 

259 @property 

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

261 """ 

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

263 

264 :returns: The value of a node. 

265 """ 

266 return self._value 

267 

268 @Value.setter 

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

270 self._value = value 

271 

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

273 """ 

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

275 

276 :param key: The key to look for. 

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

278 """ 

279 return self._dict[key] 

280 

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

282 """ 

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

284 

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

286 

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

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

289 """ 

290 self._dict[key] = value 

291 

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

293 """ 

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

295 

296 """ 

297 del self._dict[key] 

298 

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

300 """ 

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

302 

303 """ 

304 return key in self._dict 

305 

306 def __len__(self) -> int: 

307 """ 

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

309 

310 :returns: Number of attached attributes. 

311 """ 

312 return len(self._dict) 

313 

314 @readonly 

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

316 """ 

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

318 

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

320 """ 

321 return self._root 

322 

323 @property 

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

325 """ 

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

327 

328 .. note:: 

329 

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

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

332 

333 :returns: The parent of a node. 

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

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

336 """ 

337 return self._parent 

338 

339 @Parent.setter 

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

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

342 

343 if parent is None: 

344 self._nodesWithID = {} 

345 self._nodesWithoutID = [] 

346 self._level = 0 

347 

348 if self._id is None: 

349 self._nodesWithoutID.append(self) 

350 self._root._nodesWithoutID.remove(self) 

351 else: 

352 self._nodesWithID[self._id] = self 

353 del self._nodesWithID[self._id] 

354 

355 for sibling in self.GetDescendants(): 

356 sibling._root = self 

357 sibling._level = sibling._parent._level + 1 

358 if sibling._id is None: 

359 self._nodesWithoutID.append(sibling) 

360 self._root._nodesWithoutID.remove(sibling) 

361 else: 

362 self._nodesWithID[sibling._id] = sibling 

363 del self._nodesWithID[sibling._id] 

364 

365 self._parent._children.remove(self) 

366 

367 self._root = self 

368 self._parent = None 

369 elif not isinstance(parent, Node): 

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

371 if version_info >= (3, 11): # pragma: no cover 

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

373 raise ex 

374 else: 

375 if parent._root is self._root: 

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

377 

378 self._root = parent._root 

379 self._parent = parent 

380 self._level = parent._level + 1 

381 for node in self.GetDescendants(): 

382 node._level = node._parent._level + 1 

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

384 self._nodesWithID = self._nodesWithoutID = None 

385 parent._children.append(self) 

386 

387 @readonly 

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

389 """ 

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

391 

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

393 

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

395 

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

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

398 """ 

399 if self._parent is None: 

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

401 

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

403 

404 @readonly 

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

406 """ 

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

408 

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

410 

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

412 

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

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

415 """ 

416 if self._parent is None: 

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

418 

419 result = [] 

420 for node in self._parent: 

421 if node is not self: 

422 result.append(node) 

423 else: 

424 break 

425 else: 

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

427 

428 return tuple(result) 

429 

430 @readonly 

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

432 """ 

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

434 

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

436 

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

438 

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

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

441 """ 

442 if self._parent is None: 

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

444 

445 result = [] 

446 iterator = iter(self._parent) 

447 for node in iterator: 

448 if node is self: 

449 break 

450 else: 

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

452 

453 for node in iterator: 

454 result.append(node) 

455 

456 return tuple(result) 

457 

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

459 """ 

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

461 

462 :meta private: 

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

464 """ 

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

466 

467 node = self 

468 while node is not None: 

469 path.appendleft(node) 

470 node = node._parent 

471 

472 return path 

473 

474 @readonly 

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

476 """ 

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

478 

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

480 """ 

481 return tuple(self._GetPathAsLinkedList()) 

482 

483 @readonly 

484 def Level(self) -> int: 

485 """ 

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

487 

488 The level is the distance to the root node. 

489 

490 :returns: The node's level. 

491 """ 

492 return self._level 

493 

494 @readonly 

495 def Size(self) -> int: 

496 """ 

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

498 

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

500 """ 

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

502 

503 @readonly 

504 def IsRoot(self) -> bool: 

505 """ 

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

507 

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

509 """ 

510 return self._parent is None 

511 

512 @readonly 

513 def IsLeaf(self) -> bool: 

514 """ 

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

516 

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

518 """ 

519 return len(self._children) == 0 

520 

521 @readonly 

522 def HasChildren(self) -> bool: 

523 """ 

524 Returns true, if the node has child nodes. 

525 

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

527 """ 

528 return len(self._children) > 0 

529 

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

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

532 if nodeID in self._root._nodesWithID: 

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

534 else: 

535 self._root._nodesWithID[nodeID] = node 

536 node._root = self._root 

537 

538 for node in nodesWithoutIDs: 

539 self._root._nodesWithoutID.append(node) 

540 node._root = self._root 

541 

542 def AddChild(self, child: 'Node'): 

543 """ 

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

545 

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

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

548 

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

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

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

552 

553 .. seealso:: 

554 

555 :attr:`Parent` |br| 

556 |rarr| Set the parent of a node. 

557 :meth:`AddChildren` |br| 

558 |rarr| Add multiple children at once. 

559 """ 

560 if not isinstance(child, Node): 

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

562 if version_info >= (3, 11): # pragma: no cover 

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

564 raise ex 

565 

566 if child._root is self._root: 

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

568 

569 child._root = self._root 

570 child._parent = self 

571 child._level = self._level + 1 

572 for node in child.GetDescendants(): 

573 node._level = node._parent._level + 1 

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

575 child._nodesWithID = child._nodesWithoutID = None 

576 self._children.append(child) 

577 

578 def AddChildren(self, children: Iterable['Node']): 

579 """ 

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

581 

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

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

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

585 

586 .. seealso:: 

587 

588 :attr:`Parent` |br| 

589 |rarr| Set the parent of a node. 

590 :meth:`AddChild` |br| 

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

592 """ 

593 for child in children: 

594 if not isinstance(child, Node): 

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

596 if version_info >= (3, 11): # pragma: no cover 

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

598 raise ex 

599 

600 if child._root is self._root: 

601 # TODO: create a more specific exception 

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

603 

604 child._root = self._root 

605 child._parent = self 

606 child._level = self._level + 1 

607 for node in child.GetDescendants(): 

608 node._level = node._parent._level + 1 

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

610 child._nodesWithID = child._nodesWithoutID = None 

611 self._children.append(child) 

612 

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

614 """ 

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

616 

617 """ 

618 for node in self._GetPathAsLinkedList(): 

619 yield node 

620 

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

622 """ 

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

624 

625 """ 

626 node = self._parent 

627 while node is not None: 

628 yield node 

629 node = node._parent 

630 

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

632 """ 

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

634 

635 """ 

636 if isinstance(others, Node): 

637 # Check for trivial case 

638 if others is self: 

639 for node in self._GetPathAsLinkedList(): 

640 yield node 

641 return 

642 

643 # Check if both are in the same tree. 

644 if self._root is not others._root: 

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

646 

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

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

649 if left is right: 

650 yield left 

651 else: 

652 return 

653 elif isinstance(others, Iterable): 

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

655 

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

657 """ 

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

659 

660 :returns: A generator to iterate all children. 

661 

662 .. seealso:: 

663 

664 :meth:`GetDescendants` |br| 

665 |rarr| Iterate all descendants. 

666 :meth:`IterateLevelOrder` |br| 

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

668 :meth:`IteratePreOrder` |br| 

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

670 :meth:`IteratePostOrder` |br| 

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

672 """ 

673 for child in self._children: 

674 yield child 

675 

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

677 """ 

678 A generator to iterate all siblings. 

679 

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

681 

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

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

684 """ 

685 if self._parent is None: 

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

687 

688 for node in self._parent: 

689 if node is self: 

690 continue 

691 

692 yield node 

693 

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

695 """ 

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

697 

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

699 

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

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

702 """ 

703 if self._parent is None: 

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

705 

706 for node in self._parent: 

707 if node is self: 

708 break 

709 

710 yield node 

711 else: 

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

713 

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

715 """ 

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

717 

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

719 

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

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

722 """ 

723 if self._parent is None: 

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

725 

726 iterator = iter(self._parent) 

727 for node in iterator: 

728 if node is self: 

729 break 

730 else: 

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

732 

733 for node in iterator: 

734 yield node 

735 

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

737 """ 

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

739 it doesn't include the node itself. 

740 

741 :returns: A generator to iterate all descendants. 

742 

743 .. seealso:: 

744 

745 :meth:`GetChildren` |br| 

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

747 :meth:`IterateLevelOrder` |br| 

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

749 :meth:`IteratePreOrder` |br| 

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

751 :meth:`IteratePostOrder` |br| 

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

753 """ 

754 for child in self._children: 

755 yield child 

756 yield from child.GetDescendants() 

757 

758 def GetRelatives(self): 

759 """ 

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

761 

762 :returns: A generator to iterate all relatives. 

763 """ 

764 for node in self.GetSiblings(): 

765 yield node 

766 yield from node.GetDescendants() 

767 

768 def GetLeftRelatives(self): 

769 """ 

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

771 

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

773 """ 

774 for node in self.GetLeftSiblings(): 

775 yield node 

776 yield from node.GetDescendants() 

777 

778 def GetRightRelatives(self): 

779 """ 

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

781 

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

783 """ 

784 for node in self.GetRightSiblings(): 

785 yield node 

786 yield from node.GetDescendants() 

787 

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

789 """ 

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

791 

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

793 """ 

794 for child in self._children: 

795 if child.IsLeaf: 

796 yield child 

797 else: 

798 yield from child.IterateLeafs() 

799 

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

801 """ 

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

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

804 

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

806 

807 .. seealso:: 

808 

809 :meth:`GetChildren` |br| 

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

811 :meth:`GetDescendants` |br| 

812 |rarr| Iterate all descendants. 

813 :meth:`IteratePreOrder` |br| 

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

815 :meth:`IteratePostOrder` |br| 

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

817 """ 

818 queue = deque([self]) 

819 while queue: 

820 currentNode = queue.pop() 

821 yield currentNode 

822 for node in currentNode._children: 

823 queue.appendleft(node) 

824 

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

826 """ 

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

828 also the node itself as the first returned node. 

829 

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

831 

832 .. seealso:: 

833 

834 :meth:`GetChildren` |br| 

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

836 :meth:`GetDescendants` |br| 

837 |rarr| Iterate all descendants. 

838 :meth:`IterateLevelOrder` |br| 

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

840 :meth:`IteratePostOrder` |br| 

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

842 """ 

843 yield self 

844 for child in self._children: 

845 yield from child.IteratePreOrder() 

846 

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

848 """ 

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

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

851 

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

853 

854 .. seealso:: 

855 

856 :meth:`GetChildren` |br| 

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

858 :meth:`GetDescendants` |br| 

859 |rarr| Iterate all descendants. 

860 :meth:`IterateLevelOrder` |br| 

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

862 :meth:`IteratePreOrder` |br| 

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

864 """ 

865 for child in self._children: 

866 yield from child.IteratePostOrder() 

867 yield self 

868 

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

870 """ 

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

872 

873 :param other: Node to walk to. 

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

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

876 """ 

877 # Check for trivial case 

878 if other is self: 

879 yield from () 

880 

881 # Check if both are in the same tree. 

882 if self._root is not other._root: 

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

884 

885 # Compute both paths to the root. 

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

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

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

889 index = len(otherPath) 

890 for node in self.GetAncestors(): 

891 try: 

892 index = otherPath.index(node) 

893 break 

894 except ValueError: 

895 yield node 

896 

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

898 yield otherPath[i] 

899 

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

901 """ 

902 Lookup a node by its unique ID. 

903 

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

905 :returns: Node for the given ID. 

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

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

908 """ 

909 if nodeID is None: 

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

911 

912 return self._root._nodesWithID[nodeID] 

913 

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

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

916 

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

918 """ 

919 Returns an iterator to iterate all child nodes. 

920 

921 :returns: Children iterator. 

922 """ 

923 return iter(self._children) 

924 

925 def __len__(self) -> int: 

926 """ 

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

928 

929 :returns: Number of child nodes. 

930 """ 

931 return len(self._children) 

932 

933 def __repr__(self) -> str: 

934 """ 

935 Returns a detailed string representation of the node. 

936 

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

938 """ 

939 nodeID = parent = value = "" 

940 if self._id is not None: 

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

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

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

944 if self._value is not None: 

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

946 

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

948 

949 def __str__(self) -> str: 

950 """ 

951 Return a string representation of the node. 

952 

953 Order of resolution: 

954 

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

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

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

958 

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

960 """ 

961 if self._value is not None: 

962 return str(self._value) 

963 elif self._id is not None: 

964 return str(self._id) 

965 else: 

966 return self.__repr__() 

967 

968 def Render( 

969 self, 

970 prefix: str = "", 

971 lineend: str = "\n", 

972 nodeMarker: str = "├─", 

973 lastNodeMarker: str = "└─", 

974 bypassMarker: str = "│ " 

975 ) -> str: 

976 """ 

977 Render the tree as ASCII art. 

978 

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

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

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

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

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

984 :return: A rendered tree as multiline string. 

985 """ 

986 emptyMarker = " " * len(bypassMarker) 

987 

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

989 result = [] 

990 

991 if node.HasChildren: 

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

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

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

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

996 

997 # last child node 

998 child = node._children[-1] 

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

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

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

1002 

1003 return result 

1004 

1005 # Root element 

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

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

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

1009 

1010 return "".join(result)